# API Reference (/api)
The Saber API exposes company research, contact enrichment, signal subscriptions, connectors, lists, and scoring through a single authenticated REST surface.
## Enrichments [#enrichments]
## Monitors [#monitors]
## Contacts [#contacts]
## Legacy (deprecated) [#legacy-deprecated]
## Lists [#lists]
## Platform [#platform]
# Authentication (/authentication)
All API requests require authentication using an API key. Include your API key in the `Authorization` header:
```
Authorization: Bearer sk_live_your_api_key_here
```
## Getting your API key [#getting-your-api-key]
1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)
2. Navigate to **Settings** → **API Keys**
3. Click **Create API Key** and provide a name
4. Copy the key immediately — it will only be shown once
5. Store the key securely (environment variable, secret manager, or vault)
## Security best practices [#security-best-practices]
**Do:**
* Store keys securely using environment variables or secret managers
* Use different API keys for different environments (staging, production)
* Monitor API usage logs regularly
* Rotate keys periodically
**Don't:**
* Hardcode API keys in your source code
* Share API keys in public repositories or documentation
* Use the same API key across multiple external systems
* Commit API keys to version control
# Contact research, Sales Navigator search, and preferred sources (/changelog/2025-03-26-contact-research-sales-navigator-and-preferred-sources)
Saber improved contact research with Sales Navigator search support, preferred sources, and better contact profile context.
## What changed [#what-changed]
* Added Sales Navigator search support.
* Added preferred sources for research.
* Improved contact profile context.
## Who is affected [#who-is-affected]
Sales and RevOps teams researching contacts.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Contacts](/contacts)
* [Search Contacts](/contacts/search)
* [Contact Signals](/contacts/signals)
* [Connectors](/connectors)
# Use case library and account overview improvements (/changelog/2025-04-03-use-case-library-and-account-overview)
Saber added a use case library and improved account overview pages so teams can start research faster.
## What changed [#what-changed]
* Added a use case library.
* Improved account overview pages.
* Improved contact search and label suggestions.
## Who is affected [#who-is-affected]
Sales and GTM teams using account research.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Signal Discovery](/skills/reference/signal-discovery)
# Personalized outreach and HubSpot sync (/changelog/2025-04-25-personalized-outreach-and-hubspot-sync)
Saber improved personalized outreach, added more language support, and made HubSpot sync more useful for sales teams.
## What changed [#what-changed]
* Expanded personalized outreach language support.
* Improved HubSpot syncing.
* Improved contact search and outreach review.
## Who is affected [#who-is-affected]
Sales, RevOps, and GTM teams using outreach and HubSpot.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [Contacts](/contacts)
* [Contact Signals](/contacts/signals)
* [Write Outreach](/skills/reference/write-outreach)
* [Sync To HubSpot](/skills/reference/sync-to-hubspot)
# Territory settings and source freshness (/changelog/2025-05-29-territory-and-source-freshness-improvements)
Saber added territory-aware search improvements and clearer source freshness indicators for account research.
## What changed [#what-changed]
* Added territory-aware contact search improvements.
* Added clearer source freshness indicators.
* Improved account tiering context.
## Who is affected [#who-is-affected]
Sales and RevOps teams managing territories and source quality.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Search Contacts](/contacts/search)
* [Company Signals](/companies/signals)
# Shared research templates (/changelog/2025-06-16-shared-research-templates)
Teams can use shared research templates to keep account research consistent across users and use cases.
## What changed [#what-changed]
* Added shared research templates.
* Made account research setup more consistent across teams.
* Improved reusable question sets for common sales use cases.
## Who is affected [#who-is-affected]
Sales and GTM teams standardizing account research.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Research Account](/skills/reference/research-account)
# Salesforce and CRM sync improvements (/changelog/2025-06-26-salesforce-and-crm-sync-improvements)
Saber improved Salesforce and CRM syncing so connected teams can keep account and contact data more consistent.
## What changed [#what-changed]
* Improved Salesforce account and contact syncing.
* Improved CRM sync reliability.
* Added clearer completion feedback for connected CRM activity.
## Who is affected [#who-is-affected]
Salesforce and HubSpot users.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
# Salesforce connector settings and webhooks (/changelog/2025-07-23-salesforce-connector-settings-and-webhooks)
Salesforce-connected teams gained clearer connector settings, account sync support, and webhook options.
## What changed [#what-changed]
* Added Salesforce connector settings.
* Improved account syncing for Salesforce-connected teams.
* Added webhook support for Salesforce-related activity.
## Who is affected [#who-is-affected]
Salesforce users, RevOps teams, and developers.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
# Account research history and editable outreach (/changelog/2025-08-11-account-research-history-outreach-and-integrations)
Saber added saved account research history, editable outreach, and clearer integration controls for sales teams.
## What changed [#what-changed]
* Added saved account research history.
* Added editable outreach drafts.
* Improved integration controls for connected teams.
## Who is affected [#who-is-affected]
Sales, RevOps, and GTM teams using Saber for account research.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Contact Signals](/contacts/signals)
* [Connectors](/connectors)
* [Write Outreach](/skills/reference/write-outreach)
# Self-serve onboarding and faster enrichment (/changelog/2025-08-29-self-serve-onboarding-and-faster-enrichment)
New teams can get started with Saber more easily, and enrichment runs return results faster.
## What changed [#what-changed]
* Improved self-serve onboarding.
* Improved enrichment speed.
* Made first-time setup easier for new workspaces.
## Who is affected [#who-is-affected]
New workspaces, sales teams, and RevOps teams.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Getting Started](/getting-started/introduction)
* [Company Signals](/companies/signals)
# List prompts and research activity (/changelog/2025-09-18-list-prompting-and-research-activity-stream)
Saber made list-based research easier to guide and added clearer activity history for research sessions.
## What changed [#what-changed]
* Improved prompts for list-based research.
* Added clearer research activity history.
* Made list review easier for teams managing many accounts.
## Who is affected [#who-is-affected]
Sales and RevOps teams using lists for account research.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Lists](/companies/lists)
* [Company Signals](/companies/signals)
# Webhook delivery logs and manual triggers (/changelog/2025-09-29-webhook-delivery-logs-and-manual-triggers)
Developers can inspect webhook delivery history and manually trigger webhook deliveries when testing or troubleshooting integrations.
## What changed [#what-changed]
* Added webhook delivery logs.
* Added manual webhook triggering.
* Made integration testing and troubleshooting easier.
## Who is affected [#who-is-affected]
Developers and RevOps teams using Saber webhooks.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [API Reference](/api)
# API access for Saber signals (/changelog/2025-10-20-api-keys-and-signals-functionality)
Saber introduced the first API access needed for customers to use signals outside the web app.
## What changed [#what-changed]
* Introduced early API key support.
* Added early signal access for API customers.
* Started the path toward external signal integrations.
## Who is affected [#who-is-affected]
Developers and RevOps teams preparing API integrations.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [API Reference](/api)
* [Authentication](/authentication)
# API keys and Signals API (/changelog/2025-10-29-api-keys-and-signals-api)
Developers can create API keys and use the Signals API to connect Saber signals to external tools.
## What changed [#what-changed]
* Added API key management.
* Added public Signals API access.
* Made Saber signals available for external integrations.
## Who is affected [#who-is-affected]
Developers and RevOps teams integrating Saber with external tools.
## Customer action [#customer-action]
Create API keys before connecting external tools to Saber.
## Learn more [#learn-more]
* [API Reference](/api)
* [Authentication](/authentication)
* [Rate Limits](/rate-limits)
# Easier asynchronous signal results (/changelog/2025-11-12-easier-asynchronous-signal-results)
The Signals API can now return results more smoothly for longer-running signal requests.
## What changed [#what-changed]
* Improved result handling for longer-running signal requests.
* Reduced custom retry logic for API customers.
* Made asynchronous signal integrations easier to operate.
## Who is affected [#who-is-affected]
Developers using the Signals API.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [API Reference](/api)
# Reusable signal templates (/changelog/2025-11-18-signal-templates-and-diffs)
Saber added reusable signal templates and clearer change tracking, helping teams keep signal definitions consistent.
## What changed [#what-changed]
* Added reusable signal templates.
* Added clearer tracking for changes between signal definitions.
* Improved consistency for teams managing many signals.
## Who is affected [#who-is-affected]
RevOps and GTM teams managing repeatable signal definitions.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Signal Templates](/mcp/capabilities/signal-templates)
# Run Saber signals from HubSpot (/changelog/2025-11-26-run-saber-signals-from-hubspot)
HubSpot users can run Saber signals from inside HubSpot and review richer signal summaries in the CRM.
## What changed [#what-changed]
* Added the ability to run Saber signals from HubSpot.
* Improved signal summaries inside the CRM.
* Reduced switching between HubSpot and Saber.
## Who is affected [#who-is-affected]
HubSpot users, sales teams, and RevOps teams.
## Customer action [#customer-action]
Connect or refresh the HubSpot app if your team wants to run signals from HubSpot.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [Sync To HubSpot](/skills/reference/sync-to-hubspot)
# Market Signals API early access (/changelog/2025-12-18-public-developer-docs-and-market-signals)
Saber introduced early Market Signals API access for teams that want to retrieve and monitor market signals programmatically.
## What changed [#what-changed]
* Introduced early Market Signals API access.
* Added API support for market signal subscriptions.
* Made market signal results available for external systems.
## Who is affected [#who-is-affected]
Developers and RevOps teams using market signals.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [API Reference](/api)
* [Company Signals](/companies/signals)
# Hiring signals for company research (/changelog/2025-12-31-job-search-evidence-for-company-signals)
Saber improved hiring and job-posting signals so teams can better identify companies showing growth or hiring intent.
## What changed [#what-changed]
* Improved hiring signal coverage.
* Added clearer job-posting evidence.
* Made growth-related company signals easier to evaluate.
## Who is affected [#who-is-affected]
Sales and RevOps teams using company signals.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
# LinkedIn post market signals (/changelog/2026-01-09-linkedin-post-market-signals)
Saber added LinkedIn post signals so teams can monitor relevant public activity from accounts and buyers.
## What changed [#what-changed]
* Added LinkedIn post signal coverage.
* Improved filtering for relevant LinkedIn activity.
* Added another source for account and buyer monitoring.
## Who is affected [#who-is-affected]
Sales and RevOps teams monitoring account activity.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
# More structured signal results (/changelog/2026-01-23-structured-signal-results)
Saber made signal results more structured, helping API customers use results more consistently in external systems.
## What changed [#what-changed]
* Improved structured signal output.
* Made results easier to consume in external systems.
* Moved more customer-facing research activity into the signals experience.
## Who is affected [#who-is-affected]
Developers and RevOps teams using Saber signal results.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [API Reference](/api)
# HubSpot field mapping for completed signals (/changelog/2026-02-06-hubspot-field-mapping-for-signal-completion)
HubSpot-connected teams can map completed Saber signal results back into CRM fields more easily.
## What changed [#what-changed]
* Improved HubSpot field mapping for completed signals.
* Made CRM updates clearer after signal runs.
* Reduced manual transfer of signal results into HubSpot.
## Who is affected [#who-is-affected]
HubSpot users and RevOps teams.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [Sync To HubSpot](/skills/reference/sync-to-hubspot)
# Batch signals for large account lists (/changelog/2026-02-15-async-batch-signals-for-large-lists)
The Signals API can now handle larger account lists asynchronously, making high-volume signal runs easier to manage.
## What changed [#what-changed]
* Added asynchronous batch signal runs.
* Improved support for larger account lists.
* Made high-volume result handling more reliable.
## Who is affected [#who-is-affected]
Developers and RevOps teams running signals across large lists.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Lists](/companies/lists)
* [Company Signals](/companies/signals)
* [API Reference](/api)
# Saber MCP and public signal endpoints (/changelog/2026-02-20-saber-mcp-and-public-signal-endpoints)
Developers can connect Saber signals to external tools through MCP and public API endpoints.
## What changed [#what-changed]
* Added public signal endpoints.
* Added Saber MCP support.
* Made Saber signals easier to use outside the web app.
## Who is affected [#who-is-affected]
Developers and AI-tool users connecting Saber to external tools.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [MCP](/mcp)
* [API Reference](/api)
# Contact engagement signals (/changelog/2026-02-24-contact-engagement-signals)
Saber added contact engagement signals so teams can identify relevant buyer activity at the person level.
## What changed [#what-changed]
* Added contact engagement signal coverage.
* Improved buyer-level activity context.
* Helped teams prioritize individual contacts.
## Who is affected [#who-is-affected]
Sales teams researching contacts and buying committees.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Contact Signals](/contacts/signals)
* [Contacts](/contacts)
# Saber MCP support for more clients (/changelog/2026-02-27-multiple-mcp-clients-and-smithery-readiness)
Saber MCP support expanded to more client environments, making Saber easier to connect to agent tools.
## What changed [#what-changed]
* Expanded MCP client support.
* Improved MCP connection reliability.
* Prepared Saber for easier MCP setup in supported clients.
## Who is affected [#who-is-affected]
Developers and AI-tool users connecting Saber through MCP.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [MCP](/mcp)
* [MCP Installation](/mcp/getting-started/installation)
# Scheduled signals and contact lists (/changelog/2026-03-03-scheduled-signals-contact-lists-and-subscription-actions)
Teams can now monitor companies and contacts on a recurring schedule, with clearer controls for subscriptions and contact lists.
## What changed [#what-changed]
* Added scheduled signal monitoring.
* Improved contact list support.
* Added clearer subscription controls.
## Who is affected [#who-is-affected]
Sales and RevOps teams tracking accounts or contacts over time.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Contact Signals](/contacts/signals)
* [Contact Lists](/contacts/lists)
# Market signals for news, filings, IPOs, and RSS (/changelog/2026-03-12-market-signals-for-news-filings-ipos-and-rss)
Saber added market signal coverage for news, company filings, IPO activity, and RSS sources.
## What changed [#what-changed]
* Added news-based market signals.
* Added company filing and IPO signal coverage.
* Added RSS source support for market monitoring.
## Who is affected [#who-is-affected]
Sales and RevOps teams monitoring account and market activity.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
# Saber CLI sign-in and command updates (/changelog/2026-03-16-saber-cli-sign-in-and-command-updates)
The Saber CLI gained more reliable sign-in and command behavior for customers who use Saber from local terminals or agent tools.
## What changed [#what-changed]
* Improved CLI sign-in behavior.
* Improved command reliability for local use.
* Made Saber easier to use from terminal and agent environments.
## Who is affected [#who-is-affected]
Developers and AI-tool users using the Saber CLI or MCP.
## Customer action [#customer-action]
Update to the latest Saber CLI before running new commands.
## Learn more [#learn-more]
* [CLI](/cli)
* [MCP](/mcp)
* [Skills](/skills)
# Invoices and credits for market signals (/changelog/2026-03-26-billing-invoices-and-market-signal-credits)
Workspace owners can now review invoice and credit activity tied to market signal usage more clearly.
## What changed [#what-changed]
* Improved invoice visibility.
* Improved credit tracking for market signal usage.
* Made usage-based billing easier to reconcile.
## Who is affected [#who-is-affected]
Workspace owners, finance teams, and RevOps teams.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
# Market signal detail pages (/changelog/2026-04-02-market-signal-detail-pages)
Market signals now have clearer detail pages so teams can inspect context, evidence, and results before acting on a signal.
## What changed [#what-changed]
* Added richer market signal detail pages.
* Improved visibility into signal context and evidence.
* Made market signal review easier before taking action.
## Who is affected [#who-is-affected]
Sales and RevOps teams using market signals.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
# Legacy research and messaging surfaces retired (/changelog/2026-04-03-legacy-research-and-messaging-surfaces-retired)
Older research and messaging pages were removed so customers use the current Saber experience for account signals, contact signals, and outreach.
## What changed [#what-changed]
* Removed older research pages from the web app.
* Removed older messaging pages from the web app.
* Consolidated customer activity around the current Saber product experience.
## Who is affected [#who-is-affected]
Teams still using older research or messaging pages.
## Customer action [#customer-action]
Use the current company signals, contact signals, and outreach pages.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Contact Signals](/contacts/signals)
* [Skills](/skills)
# Stronger account security controls (/changelog/2026-04-09-stronger-account-security-controls)
Saber added stronger controls for account access and sign-in, improving protection for customer workspaces.
## What changed [#what-changed]
* Improved account access controls.
* Strengthened sign-in and workspace protection.
* Reduced the risk of unintended access.
## Who is affected [#who-is-affected]
All Saber users and workspace owners.
## Customer action [#customer-action]
Review team access and sign-in settings if you manage a workspace.
## Learn more [#learn-more]
* [Authentication](/authentication)
# HubSpot deal stage triggers (/changelog/2026-04-20-hubspot-deal-stage-triggers)
HubSpot-connected teams can now use deal stage changes as a trigger for Saber activity, keeping sales intelligence aligned with deal movement.
## What changed [#what-changed]
* Added support for deal stage changes from HubSpot.
* Helped teams keep account context aligned with deal updates.
* Improved CRM-connected sales intelligence.
## Who is affected [#who-is-affected]
HubSpot users and RevOps teams.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
# Clearer rate limits and billing reconciliation (/changelog/2026-04-23-rate-limit-and-billing-reconciliation-updates)
Saber improved usage visibility for API and billing activity, making it easier for teams to understand limits, credits, and checkout state.
## What changed [#what-changed]
* Improved API rate-limit visibility.
* Improved credit and billing reconciliation.
* Made checkout state easier to understand for usage-based customers.
## Who is affected [#who-is-affected]
Developers, workspace owners, finance teams, and RevOps teams.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Rate Limits](/rate-limits)
* [API Reference](/api)
# Scoring support for API and HubSpot users (/changelog/2026-04-29-scoring-support-for-api-and-hubspot-users)
Scoring became available to API and HubSpot-connected teams, helping customers bring Saber prioritization into their existing systems.
## What changed [#what-changed]
* Made scoring available for API-based use cases.
* Improved HubSpot-connected access to sales intelligence.
* Supported more consistent prioritization across Saber and connected systems.
## Who is affected [#who-is-affected]
Developers, RevOps teams, and HubSpot-connected teams.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [API Reference](/api)
# Browser extension v2 (/changelog/2026-04-30-browser-extension-v2)
Saber shipped a refreshed browser extension experience with improved sign-in behavior and more reliable access to Saber context in supported tools.
## What changed [#what-changed]
* Refreshed the browser extension experience.
* Improved sign-in behavior.
* Prepared the extension for richer in-page account context.
## Who is affected [#who-is-affected]
Sales teams using the Saber browser extension.
## Customer action [#customer-action]
Update or reinstall the extension if prompted by the browser.
## Learn more [#learn-more]
* [Connectors](/connectors)
# Signal scoring for account prioritization (/changelog/2026-05-01-signal-scoring-for-account-prioritization)
Saber added scoring support so teams can rank accounts and contacts by signal strength instead of reviewing every result manually.
## What changed [#what-changed]
* Added scoring support for signal-based prioritization.
* Made it easier to compare accounts by signal strength.
* Improved the path from signal results to prioritized action.
## Who is affected [#who-is-affected]
Sales, RevOps, and developer teams using Saber signals.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [API Reference](/api)
# Department and seniority filters for contact search (/changelog/2026-05-03-contact-search-department-and-seniority-filters)
Contact search now supports department and seniority filters, helping teams find the right buying roles faster.
## What changed [#what-changed]
* Added department filters to contact search.
* Added seniority filters to contact search.
* Improved targeting for role-specific prospecting.
## Who is affected [#who-is-affected]
Sales and RevOps teams building contact lists.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Search Contacts](/contacts/search)
* [Contact Lists](/contacts/lists)
# Browser extension side panel (/changelog/2026-05-12-browser-extension-side-panel-launch)
The browser extension now opens as an in-page side panel, giving teams more space to review Saber context while they work in supported sales tools.
## What changed [#what-changed]
* Moved extension interactions into a larger side panel.
* Improved the in-browser account review experience.
* Kept Saber context visible alongside supported sales pages.
## Who is affected [#who-is-affected]
Sales teams using the Saber browser extension.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
# Browser extension signal feed for HubSpot (/changelog/2026-05-18-browser-extension-signal-feed-and-hubspot-panel)
The Saber browser extension now shows relevant company signals directly in HubSpot, so sales teams can review account context without leaving the CRM.
## What changed [#what-changed]
* Added a signal feed inside supported HubSpot company pages.
* Made the side panel aware of the current HubSpot company record.
* Reduced switching between HubSpot and Saber during account review.
## Who is affected [#who-is-affected]
Sales and RevOps teams using HubSpot.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [Company Signals](/companies/signals)
# HubSpot panel account summaries (/changelog/2026-05-20-hubspot-panel-account-summaries)
The Saber browser extension now adds account summaries to the HubSpot panel, making it easier for sellers to understand account context before reviewing individual signals.
## What changed [#what-changed]
* Added account summaries to supported HubSpot company record pages.
* Placed summarized account context above the detailed signal feed.
* Refined the panel experience so teams can scan account context faster from the CRM.
## Who is affected [#who-is-affected]
Sales and RevOps teams using the Saber browser extension in HubSpot.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [Company Signals](/companies/signals)
# Public docs portal (/changelog/2026-05-20-public-docs-portal)
Saber launched a public docs portal with product guides, API documentation, MCP resources, CLI documentation, skills guidance, and changelog-ready static publishing.
## What changed [#what-changed]
* Added a public documentation portal for Saber product and developer content.
* Published product guides, API documentation, MCP resources, CLI documentation, and skills guidance in one place.
* Added static publishing support for docs pages and changelog content.
## Who is affected [#who-is-affected]
All customers, developers, RevOps teams, MCP users, and CLI users.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Documentation](/)
* [API Reference](/api)
# Email Finder API (/changelog/2026-05-21-email-finder-api)
Saber added an Email Finder API so teams can programmatically enrich contacts with verified email discovery workflows.
## What changed [#what-changed]
* Added a public API path for email discovery.
* Made Email Finder available to developer workflows that need contact enrichment outside the web app.
* Added operational visibility for Email Finder requests.
## Who is affected [#who-is-affected]
Developers and RevOps teams using Saber contact enrichment.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [API Reference](/api)
* [Contact Search](/contacts/search)
# More reliable MCP streaming sessions (/changelog/2026-05-21-more-reliable-mcp-streaming-sessions)
Saber MCP streaming sessions now stay connected more reliably when clients or networks leave server-sent event streams idle.
## What changed [#what-changed]
* Improved the reliability of long-lived MCP streaming connections.
* Reduced idle disconnects for clients that keep an MCP stream open between tool calls.
* Kept the change transparent for existing MCP client setups.
## Who is affected [#who-is-affected]
Teams using Saber MCP with streaming-capable clients.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [MCP](/mcp)
* [MCP Basic Usage](/mcp/getting-started/basic-usage)
# Contact research, docs routing, and connector reliability (/changelog/2026-05-28-contact-research-docs-routing-and-connector-reliability)
Saber added contact-level research lookups, improved public docs routing, and made Sales Navigator connector sessions more resilient.
## What changed [#what-changed]
* Added contact research and contact signal lookups by email, LinkedIn URL, and external CRM identifier.
* Improved public docs routing and redirects from legacy developer documentation paths.
* Improved expired and non-Sales LinkedIn session handling for Sales Navigator connectors, reducing repeated reconnect loops.
## Who is affected [#who-is-affected]
Developers, RevOps teams, docs users, and teams using Sales Navigator connectors.
## Customer action [#customer-action]
Reconnect any Sales Navigator connector that is already disconnected. No action is needed for the contact research or docs routing updates.
## Learn more [#learn-more]
* [API Reference](/api)
* [Contact Signals](/contacts/signals)
* [Documentation](/)
* [Connectors](/connectors)
# More reliable signal creation and source coverage (/changelog/2026-05-29-signal-creation-timeouts-and-source-coverage)
Saber improved signal creation reliability for long-running requests and expanded company research coverage for commonly requested business sources.
## What changed [#what-changed]
* Added better handling for long-running signal creation requests so clients receive a controlled timeout response instead of a platform error.
* Improved company research coverage for business sources that often need stronger retrieval settings.
* Reduced failure noise for signal workflows that depend on those sources.
## Who is affected [#who-is-affected]
Sales, RevOps, and developer teams running company signals.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [API Reference](/api)
# API docs, contact lookup, and HubSpot sync updates (/changelog/2026-06-11-api-docs-contact-lookup-and-hubspot-sync)
Saber fixed direct access to API docs, updated contact lookup identity handling, and tightened HubSpot sync and API validation behavior.
## What changed [#what-changed]
* Fixed API documentation routes so non-browser clients and AI agents can retrieve guide pages and markdown content directly.
* Standardized contact lookup around CRM external IDs and LinkedIn URLs instead of email-only lookup requests.
* Updated malformed company-list import and signal-template requests to return 422 validation responses.
* Adjusted HubSpot CRM sync behavior for upstream rate limits, authorization faults, disconnects, and reconnects.
* Updated Email Finder handling for bursty verification requests and catch-all domains.
## Who is affected [#who-is-affected]
Developers, RevOps teams, HubSpot-connected teams, and AI-tool users.
## Customer action [#customer-action]
Stop using email-only contact lookup requests; use externalId or LinkedIn URL identifiers instead. Update API clients that treated malformed company-list or signal-template requests as server errors to handle 422 validation responses.
## Learn more [#learn-more]
* [API Reference](/api)
* [Connectors](/connectors)
* [Contact Signals](/contacts/signals)
* [Contact Search](/contacts/search)
* [Getting Started](/getting-started/introduction)
# Signal failures are now returned in the response body (/changelog/2026-06-12-signal-failures-now-returned-in-the-response-body)
The Signals API now returns HTTP `200` for any signal it can retrieve — including failed ones — and conveys failure in the response body instead of an error status code.
## What changed [#what-changed]
* `GET` and `POST /v1/companies/signals` (and `/:id`), and the contacts equivalents, now return HTTP `200` whenever the signal is retrievable, regardless of its processing state.
* A failed signal is reported in the body as `status: "failed"` together with a new `retryable` boolean:
* `retryable: true` — transient failure; the request may succeed if retried (previously surfaced as HTTP `500`).
* `retryable: false` — non-retryable failure such as validation or a rate limit; do not retry (previously surfaced as HTTP `422`).
* Failed signals no longer return `422` or `500`. Genuine request errors — malformed body, bad input, authentication, or not-found — still return their normal `400`, `401`, `404`, and `422` codes.
```json
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "failed",
"retryable": false,
"error": "LinkedIn data is currently unavailable due to rate limits.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED"
}
```
## Who is affected [#who-is-affected]
Developers and RevOps teams that call the Signals API directly and branch on the HTTP status code to detect failed signals.
## Customer action [#customer-action]
If your integration treats a `422` or `500` from a signal endpoint as a signal failure, update it to:
1. Treat a `200` as a successful retrieval of the signal, then inspect `body.status`.
2. When `body.status` is `"failed"`, read `body.retryable` to decide whether to retry: retry only when `retryable` is `true`, using your existing backoff.
3. Continue to handle `400`, `401`, `404`, and `422` as genuine request errors — these are unchanged.
This is a single coordinated cut with no backward-compatibility shim, so make the change before relying on the new behavior.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Contact Signals](/contacts/signals)
* [Rate Limits](/rate-limits)
* [API Reference](/api)
# Faster technology-based company list reads and exports (/changelog/2026-06-16-faster-technology-based-company-lists)
Company lists built with technology filters now use their materialized technology matches directly when loading pages or exporting results.
## What changed [#what-changed]
* List reads resolve the stored technology-matched domains through indexed company and alias lookups.
* Sorting and pagination run against the matched result set instead of a broad domain-or-firmographic scan.
* Page and export response shapes remain unchanged.
The update significantly reduces query work for technology-based lists and avoids timeouts seen on larger result sets. No customer action is required.
## Learn more [#learn-more]
* [Company Lists](/companies/lists)
* [Company Search](/companies/search)
* [Rate Limits](/rate-limits)
# More reliable long-running signals and large batches (/changelog/2026-06-24-long-running-signals-and-batches)
Saber increased the completion budget for long-running signal work and aligned the surrounding credit and cleanup timers with that budget.
## What changed [#what-changed]
* Company and contact signal workflows can run for up to four hours when the workload requires it.
* Signal credit claims remain valid through the execution window and settlement grace period.
* Cleanup waits for the longer workflow budget before treating a signal as orphaned.
* High-volume company-domain lookups use the matching production index, reducing database pressure during large batches.
These changes reduce premature failures and billing races for unusually large or slow workloads. No customer action is required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Contact Signals](/contacts/signals)
* [Rate Limits](/rate-limits)
# Bulk Email Finder API (/changelog/2026-07-08-bulk-email-finder-api)
Email Finder now supports asynchronous batches of up to 1,000 contacts, replacing client-side loops with a submit-and-poll workflow.
## What changed [#what-changed]
* Submit a batch with `POST /v1/contacts/find-email/batch` and poll its progress with `GET /v1/contacts/find-email/batch/{id}`.
* Read per-contact `found`, `not_found`, or `error` outcomes as rows complete.
* Receive partial results even when one domain or contact cannot be resolved.
* Rely on server-side domain grouping, pacing, and retries instead of implementing them in each client.
* Single Email Finder responses now include an explicit `found` or `not_found` status.
Batch results are retained for 30 days.
## Learn more [#learn-more]
* [Email Finder API](/api/email-finder)
* [Contact Search](/contacts/search)
* [Rate Limits](/rate-limits)
# Email Finder single calls now use fractional credits (/changelog/2026-07-08-email-finder-fractional-credit-billing)
Saber now supports fractional credit balances, and a single `POST /v1/contacts/find-email` request costs 0.1 credits when verification work is performed.
## Billing behavior [#billing-behavior]
* Found and not-found results are billed when Saber performs real verification work.
* Requests rejected by validation or short-circuited before paid verification are free.
* Failed upstream or internal work is reverted and not billed.
* Insufficient credit is returned before verification begins.
* Bulk Email Finder batches are currently unmetered; the 0.1-credit price applies only to the single-contact endpoint.
## Customer action [#customer-action]
Include the single-call price in usage estimates. CLI users should update to v0.1.12 or later so fractional balances are decoded correctly.
## Learn more [#learn-more]
* [Email Finder API](/api/email-finder)
* [Rate Limits and credits](/rate-limits)
* [Saber CLI](/cli)
# Five prebuilt company signals (/changelog/2026-07-08-five-prebuilt-company-signals)
Saber now provides five documented prebuilt company signals for common qualification, timing, and research questions.
## Available signals [#available-signals]
* Funding, including the latest public funding event and public-company status.
* M\&A, including acquisitions made by or involving the company.
* Technology, by product or technology category.
* Open Jobs, summarizing current hiring by function, location, and technology.
* Firmographics, including company size, entity type, location, category, and customer segments.
Each signal accepts a company domain and returns a stable structured answer. Synchronous endpoints return the result in the same request when it is ready; asynchronous endpoints support polling and webhooks.
## Learn more [#learn-more]
* [Prebuilt signals](/signals)
* [Signals API reference](/api/signals)
* [Custom signals](/signals/custom)
# More reliable MCP tool setup and sessions (/changelog/2026-07-08-mcp-tool-setup-and-session-reliability)
Saber MCP now starts tool sessions reliably when API operations contain schema constraints that the MCP converter cannot represent directly.
## What changed [#what-changed]
* Unsupported OpenAPI `not` constraints no longer prevent an MCP server from registering its tools.
* The upstream Saber API still validates the complete request contract when a tool is called.
* Stateful MCP sessions now use bounded memory, reducing the risk of long-lived sessions consuming excessive resources.
The update applies automatically to hosted Saber MCP sessions. No client change is required.
## Learn more [#learn-more]
* [Saber MCP](/mcp)
* [MCP basic usage](/mcp/getting-started/basic-usage)
* [Signals API reference](/api/signals)
# More accurate company matching across domains and brands (/changelog/2026-07-08-more-accurate-company-matching)
Saber improved how company identity is resolved before research and enrichment work begins.
## What changed [#what-changed]
* Shared domains such as hosting, marketplace, and profile domains are no longer treated as strong evidence that two companies are the same.
* Sub-brands and parent brands are scored directionally instead of being treated as interchangeable.
* Foreign domains are checked more carefully before a well-known company is replaced by an unrelated local or similarly named entity.
* Same-name companies with different domains are protected from cross-attributed signal evidence.
The updated matching behavior applies automatically to company signals, lists, and enrichment. No customer action is required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Company Lists](/companies/lists)
* [Company Search](/companies/search)
# IPO signals now include SEC S-1 and F-1 filings (/changelog/2026-07-08-sec-filing-coverage-for-ipo-signals)
IPO market signals now use official SEC EDGAR filings in addition to web sources.
## What changed [#what-changed]
* Discover S-1 and F-1 registration filings, including amended filings.
* Normalize issuer names, filing dates, accession numbers, and source links from SEC data.
* Use `sources: ["edgar"]` for filing-only subscriptions, or combine `web` and `edgar` for broader discovery.
* Avoid cross-source duplicates when the same IPO event appears in both web and filing evidence.
## Customer action [#customer-action]
Existing subscriptions continue to use their configured sources. Add the `edgar` source when official filing evidence should be included, or use only `edgar` when a filing is required.
## Learn more [#learn-more]
* [Market Signals API](/api/market-signals)
* [Market signal detail pages](/changelog/2026-04-02-market-signal-detail-pages)
# Saber CLI adds prebuilt signal commands and fractional credit support (/changelog/2026-07-09-saber-cli-prebuilt-signals-and-fractional-credits)
Saber CLI v0.1.13 adds direct commands for the five prebuilt company signals and includes the contact-search, subscription, and fractional-credit improvements released since v0.1.10.
## What changed [#what-changed]
* Run funding, M\&A, technology, open-jobs, and firmographics signals from the CLI, using synchronous or asynchronous execution.
* Filter contact searches by department and seniority.
* Set a structured output schema when creating a subscription.
* Read balances with fractional credits without decoding or confirmation failures.
## Customer action [#customer-action]
Run `saber update` and confirm `saber version` reports v0.1.13 or later.
## Learn more [#learn-more]
* [Saber CLI](/cli)
* [CLI command reference](/cli/commands)
* [Prebuilt signals](/signals)
* [CLI v0.1.13 release](https://github.com/saberapp/cli/releases/tag/v0.1.13)
# More reliable Sales Navigator access detection (/changelog/2026-07-09-sales-navigator-access-reliability)
Saber now checks Sales Navigator access before making a Sales Navigator request and can recover stale access information from the connected session.
## What changed [#what-changed]
* Connectors without a Sales Navigator seat are skipped before an unsupported request is made, so the expected lack of access no longer causes a spurious disconnect.
* A connector whose saved access flag is stale can recover its current subscription tier from the live session and continue normally.
* The same access check is used across contact research, contact search, contact resolution, and contact-profile signal work.
No customer action is required. Existing connected sessions use the updated behavior automatically.
## Learn more [#learn-more]
* [Connectors](/connectors)
* [Contact Search](/contacts/search)
* [Contact Signals](/contacts/signals)
# Pricing, subscription, and webhook updates (/changelog/2026-07-16-pricing-subscription-and-webhook-updates)
Saber made API pricing, subscription status, and direct webhook delivery easier to understand.
## What changed [#what-changed]
### Complete API credit pricing [#complete-api-credit-pricing]
* The [Pricing page](/pricing) lists every metered API operation and its standard credit cost.
* Signal subscription runs, market-signal detection, and technology-filtered list creation are included alongside per-request charges.
* Metered OpenAPI operations include machine-readable credit-cost metadata.
* Documentation validation prevents the published table from drifting from the product rate card.
Organizations with a custom agreement may have different rates.
### Canceled plan end dates [#canceled-plan-end-dates]
* Plans scheduled to cancel show `Plan ends on` with the effective end date.
* Renewal copy remains limited to active and trialing subscriptions.
* Past-due subscriptions retain their status and scheduled-cancellation details in the billing summary.
### Direct signal webhook guidance [#direct-signal-webhook-guidance]
* Direct signal webhooks are documented as unsigned.
* A delivery is attempted up to five times in total for retryable failures.
* Each request has a 25-second timeout.
* `X-Idempotency-Key` can be used to deduplicate repeated delivery attempts.
* Re-reading the signal through the API is the recommended verification step.
## Customer action [#customer-action]
Review the Pricing page and update any usage budgets or estimates that depend on API credit costs.
If you use direct signal webhooks, use an unguessable endpoint URL, deduplicate requests by `X-Idempotency-Key`, and verify the result through the Signals API before acting on it.
## Learn more [#learn-more]
* [Pricing](/pricing)
* [API reference](/api)
* [Rate Limits](/rate-limits)
* [Signals API reference](/api/signals)
* [Custom signals](/signals/custom)
# Technology-filtered list builds handle duplicate job data (/changelog/2026-07-20-technology-list-duplicate-job-handling)
Technology-filtered company lists now handle repeated job postings returned for the same or related companies.
## What changed [#what-changed]
* Duplicate job records are removed before the list-build database update.
* Repeated jobs within one company or across related companies no longer abort the full build.
## Customer action [#customer-action]
If a technology-filtered list failed before July 20, create a new list. Re-saving the same filters on the failed list does not restart its build.
## Learn more [#learn-more]
* [Company Lists](/companies/lists)
* [Company Search](/companies/search)
# Safer signal answers and source matching (/changelog/2026-07-21-safer-signal-answers-and-source-matching)
Saber added safeguards around answer persistence, source selection, and model availability.
## What changed [#what-changed]
* A refreshed answer is no longer replaced with an older answer when the new facts materially differ from the evidence attached to the run.
* Direct page fetches refuse obvious same-name companies on a different domain.
* Signals can return a null answer when the available evidence cannot support a result.
* Transient model-capacity failures can retry across configured EU endpoints.
The safeguards apply automatically to signal runs. No customer action is required.
## Learn more [#learn-more]
* [Company Signals](/companies/signals)
* [Contact Signals](/contacts/signals)
* [Custom signals](/signals/custom)
# Email Finder and company signal reliability updates (/changelog/2026-07-28-email-finder-and-company-signal-reliability)
Saber improved the reliability of Email Finder results and company signal evidence.
## What changed [#what-changed]
### Email Finder results and domain matching [#email-finder-results-and-domain-matching]
* Temporary greylisting or an exhausted verification budget returns `status: inconclusive` instead of a hard error or a definitive `not_found` result.
* Single and batch requests use the same `found`, `not_found`, and `inconclusive` outcome vocabulary.
* Domain recovery rejects unrelated companies, parking pages, and common noise sources before using a replacement domain.
* Optional `company_name` and `location` fields can anchor recovery to the intended company and region.
### Company signal evidence [#company-signal-evidence]
* Failed, blocked, empty, or inconclusive research is treated as unknown instead of evidence for a categorical negative.
* For questions with multiple alternatives, a negative answer requires direct evidence that rules out every alternative.
* When a question names a specific legal entity, facts about its parent, subsidiaries, customers, or suppliers are not treated as proof for that entity.
* Signal research can preserve structured JSON from registry-style sources instead of reducing it to webpage text.
## Customer action [#customer-action]
Retry `inconclusive` Email Finder results later. When a supplied domain may be stale, provide `company_name` or `location` to improve domain recovery. No action is required for company signal runs.
## Learn more [#learn-more]
* [Email Finder API](/api/email-finder)
* [Pricing](/pricing)
* [Company Signals](/companies/signals)
* [Custom signals](/signals/custom)
# Contact search adds mover and firmographic filters (/changelog/2026-08-20-contact-search-mover-and-firmographic-filters)
Contact search can now define a named-account or whitespace audience and find possible recent movers.
## What changed [#what-changed]
* Added recent job change and current-tenure filters.
* Added industry, company headcount, and company type filters.
* Added both Sales Navigator tenure values to each current position.
* Kept the existing `tenureInMonths` response field.
The tenure values are supplier data. They do not confirm a promotion or an employer change.
## Customer action [#customer-action]
No action required.
## Learn more [#learn-more]
* [Search Contacts](/contacts/search)
* [Contacts API](/api/contacts)
# Contact search says if a position is a recent move (/changelog/2026-08-26-contact-search-move-classification)
Contact search returned two tenure values for each current position, but you had to compare them yourself. Now the API does the comparison and tells you the result.
## What changed [#what-changed]
* Added `moveType` to each item in `positions[]`.
* `moveType` has four values: `moved_internally`, `joined_new_employer`, `no_recent_move`, and `unknown`.
* The classification applies to one position. A person with more than one current position gets a value for each position.
* Kept the existing `tenureAtPosition`, `tenureAtCompany`, and `tenureInMonths` fields.
## How accurate it is [#how-accurate-it-is]
We measured the classification against 795 people, and compared 175 of them with their full work history. Where we gave `moved_internally`, it was correct in all 72 cases. It finds approximately 2 of each 3 internal moves.
Some people do not put an end date on their previous role at the same employer. Sales Navigator then shows only the new role, and decreases the company tenure to agree with the position tenure. That person gets `joined_new_employer`, although the person moved internally.
`moveType` tells you that the person changed role. It does not tell you that the new role is more senior.
## Customer action [#customer-action]
No action required. `moveType` is a new field and does not change the fields you already read.
## Learn more [#learn-more]
* [Search Contacts](/contacts/search)
* [Contacts API](/api/contacts)
# Contact search consistently names the newest current role (/changelog/2026-08-31-contact-search-newest-current-role)
Some people hold several current roles at once. Contact search previously copied `role` and `companyName` from the first position Sales Navigator returned. That position was not always the newest one.
## What changed [#what-changed]
* `role` and `companyName` now come from the current position with the latest `startedOn` date.
* A start date with a year but no month counts as January.
* A dated position wins over an undated position. Ties keep the existing response order.
* Selecting the summary does not reorder or remove anything from `positions[]`.
* Contact lists save the same summary and the complete returned `positions[]` value.
## Customer action [#customer-action]
No action required. The response fields and types do not change. For a person with several current positions, the two summary values can now name a newer position than before.
## Learn more [#learn-more]
* [Search Contacts](/contacts/search)
* [Contacts API](/api/contacts)
# New discovery monitor: public tenders (/changelog/2026-09-09-tender-published-discovery-monitor)
A new discovery monitor of type `tender_published` will be available. It finds public procurement notices that you can still bid on, and it emits one signal for each notice that matches your filters.
This monitor is not available in production yet. Until Saber enables it, a request to create a `tender_published` monitor returns `422`. This entry will change when the monitor is available.
## Sources [#sources]
The monitor reads three public procurement services:
* **TED** — EU notices above the publication threshold.
* **SAM.gov** — US federal opportunities.
* **UK Contracts Finder** — UK notices.
## Scope [#scope]
The monitor finds only software, AI and automation work. It narrows the notices two times:
1. It reads only these procurement codes: CPV divisions `72`, `48`, `30` and `73` from TED and UK Contracts Finder, and NAICS codes `541511`, `541512`, `541519` and `513210` from SAM.gov.
2. A classifier examines each notice. The monitor delivers a notice only if the classifier finds software, AI or automation work in it.
Your `cpvCodes` and `naicsCodes` filters narrow this scope. They cannot make it wider. For example, a monitor with `cpvCodes: ["45"]` (construction work) and `sources: ["ted", "uk_contracts_finder"]` gets only the notices that have an in-scope code and also a construction code. A notice can have more than one CPV code, and one matching code is sufficient. Without `sources`, the same monitor also gets the SAM.gov notices in scope, because `cpvCodes` does not apply to SAM.gov.
## What it emits [#what-it-emits]
The monitor emits only notices that are still open:
* It does not emit award notices. An award notice names a contract that a supplier has already won.
* It does not emit a notice after its submission deadline.
Each signal payload contains the buyer, the country, the classification codes, the contract value, the deadline, and a link to the notice.
## Filters [#filters]
Filter by procurement code, not by keyword. A notice description is published in the language of the buyer, and it is not translated. A keyword filter therefore finds English notices and misses the others. Procurement codes have no language.
| Filter | Effect |
| -------------------- | ---------------------------------------------------------------------------------------------- |
| `sources` | One or more of `ted`, `sam_gov`, `uk_contracts_finder`. |
| `cpvCodes` | EU and UK codes. The monitor matches a prefix, so `72` selects all IT services. |
| `naicsCodes` | US codes. The monitor matches a prefix. |
| `buyerCountries` | ISO 3166-1 alpha-2 country codes. |
| `minValueEur` | A minimum contract value in EUR. The monitor compares only values that a notice states in EUR. |
| `deadlineWithinDays` | Only notices that close in this number of days. |
| `categories` | One or more of `software`, `ai`, `automation`. |
| `promptFilter` | Your own description of the work you want. |
If you do not set a filter, the monitor does not narrow on it.
The monitor reads the last two days of notices. This window is fixed.
Two rules keep opportunities in your feed:
* A notice that states no contract value, or states it in a different currency, passes `minValueEur`. Many buyers publish no budget, and Saber does not convert currencies.
* A notice that states no deadline passes `deadlineWithinDays`.
## Payload summary [#payload-summary]
Each signal contains an English summary of the notice. The summary lets you read a notice in any language without a translation step.
## Customer action [#customer-action]
No action is necessary. Your other monitors do not change. When the monitor is available, create one with `POST /v1/monitors` and a check of type `tender_published`.
## Learn more [#learn-more]
* [Monitors](/monitors)
* [Monitors API](/api/monitors)
# Changelog (/changelog)
# Authentication (/cli/authentication)
The Saber CLI authenticates with the same API keys used by the public REST API. Create a key in Saber, then save it locally for CLI use.
```bash
saber auth login
```
For non-interactive setup, pass the key directly:
```bash
saber auth login --key sk_live_...
```
## Verify your session [#verify-your-session]
```bash
saber auth status
```
Use `status` before automation or agent workflows so failures are caught before a long-running command starts.
## Logout [#logout]
```bash
saber auth logout
```
Logout removes the stored CLI credential from the machine. It does not revoke the API key in Saber. Revoke or rotate keys from the Saber API Keys page when a key should stop working everywhere.
## CI and automation [#ci-and-automation]
Use `SABER_API_KEY` or your CI secret manager for jobs that should not write credentials to disk:
```bash
export SABER_API_KEY="sk_live_..."
saber credits --json
```
Avoid printing signal output in shared logs when the results contain prospect, customer, or CRM context.
## Agent context [#agent-context]
If you use Claude Code with Saber Skills, run:
```bash
saber init-claude
```
`init-claude` writes Saber organization context and command guidance into `CLAUDE.md`, so Claude Code can use Saber Skills with the right profile, connector status, available commands, and credit guardrails.
# JSON and CI (/cli/ci-json)
All commands support `--json` for machine-readable output.
```bash
saber credits --json
saber signal -d acme.com -q "Are they hiring?" --answer-type boolean --json
```
## Shell example [#shell-example]
```bash
signal_id=$(saber signal -d acme.com -q "Are they hiring?" --no-wait --json | jq -r .id)
saber signal get "$signal_id" --json
```
## Verbose debugging [#verbose-debugging]
Use `--verbose` to inspect request routing and rate-limit headers without exposing the full API key.
```bash
saber signal -d acme.com -q "test" --verbose
```
## CI setup [#ci-setup]
```bash
saber auth login --key "$SABER_API_KEY"
saber credits --json
```
Store the key in your CI secret manager and avoid printing command output that may contain business-sensitive research results.
# Command Reference (/cli/commands)
## Global flags [#global-flags]
| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------- |
| `--json` | Print raw JSON responses to stdout. |
| `--quiet`, `-Q` | Suppress non-error output. |
| `--verbose`, `-v` | Print HTTP method, URL, masked auth header, status, and rate-limit headers to stderr. |
| `--yes`, `-y` | Skip confirmation prompts where supported. |
| `--api-url` | Override the API base URL. |
## Signals [#signals]
Run company signal research:
```bash
saber signal --domain acme.com --question "Are they hiring engineers?"
saber signal -d acme.com -q "Which CRM do they use?" --answer-type list
saber signal -d acme.com --template tmpl_123 --verification-mode strict
```
Run contact signal research:
```bash
saber signal --profile https://linkedin.com/in/jane-doe --question "What is their current role?"
```
Create async work:
```bash
saber signal -d acme.com -q "What changed recently?" --no-wait
saber signal -d acme.com -q "What changed recently?" --webhook https://example.com/webhooks/saber
```
Retrieve or browse prior signal results:
```bash
saber signal get
saber signal list --domain acme.com --status completed --limit 25
```
Batch signals combine each target with each question or template:
```bash
saber signal batch --domain acme.com --domain stripe.com --question "Are they hiring SDRs?"
saber signal batch --domain acme.com --domain stripe.com --template tmpl_123 --async
```
Important flags:
| Flag | Description |
| --------------------- | -------------------------------------------------------------------------------------------- |
| `--domain`, `-d` | Company domain. |
| `--profile`, `-p` | Contact LinkedIn profile URL. |
| `--question`, `-q` | Research question. Required unless using `--template`. |
| `--answer-type`, `-a` | `open_text`, `boolean`, `number`, `list`, `percentage`, `currency`, `url`, or `json_schema`. |
| `--template` | Reuse a signal template instead of passing a raw question. |
| `--verification-mode` | `strict` or `lenient`. |
| `--force-refresh` | Bypass the 12-hour answer cache. |
| `--no-wait` | Return an ID immediately instead of waiting. |
| `--max-wait` | Sync timeout in seconds. |
| `--webhook` | Deliver async results to a URL. |
| `--async` | Use async mode for large batch signal runs. |
| `--generate-summary` | Generate summaries after a batch completes. |
## Lists [#lists]
Create and browse company lists:
```bash
saber list company count-preview --industry "software development" --country US
saber list company create --name "US SaaS" --industry "software development" --size "51-200"
saber list company companies
saber list company export --output accounts.csv
```
Create and browse contact lists:
```bash
saber list contact create --name "VP Sales" \
--company-linkedin https://linkedin.com/company/acme \
--title "VP Sales"
saber list contact show
```
Search without creating:
```bash
saber company search --technology hubspot --country US
saber contact search --company-linkedin https://linkedin.com/company/acme --title "Head of Sales"
```
## Templates [#templates]
Templates standardise reusable signal questions.
```bash
saber template create --name "CRM Detection" --question "Which CRM are they using?" --answer-type list
saber template list
saber template update --weight important
saber template delete
```
## Market signals [#market-signals]
Market signals monitor a recurring source and deliver results to a webhook.
```bash
saber market-signal create --type JOB_POSTS --webhook https://example.com/hooks/saber --interval daily
saber market-signal pause
saber market-signal resume
saber market-signal trigger
saber market-signal signals
```
## Signal subscriptions [#signal-subscriptions]
Signal subscriptions run reusable questions or templates against a company list on a schedule.
```bash
saber subscription create --list --name "Hiring signal" --question "Are they hiring sales leaders?" --answer-type boolean --frequency weekly
saber subscription list
saber subscription trigger
saber subscription stop
saber subscription start
```
## Summaries [#summaries]
Generate or list AI summaries for a domain:
```bash
saber summary generate --domain acme.com
saber summary list --domain acme.com --limit 5
```
## Scoring [#scoring]
Scoring profiles map signal template answers to fit and urgency scores.
```bash
saber scoring profile create --type company --name "Mid-market ICP"
saber scoring rule upsert --signal-template tmpl_123 --dimension fit --answer-type boolean --true 10 --false 0
saber scoring assignment create --profile --type company --object acme.com
saber scoring scores --type company --object acme.com --detailed
```
## Organisation and account [#organisation-and-account]
```bash
saber org get
saber org update --name "Acme" --website https://acme.com
saber connectors
saber credits
```
## Updates [#updates]
```bash
saber update
saber version
```
# Saber CLI (/cli)
The Saber CLI is the fastest way to use Saber from a shell, CI job, or agent session. It wraps the public API with ergonomic commands for company signals, contact signals, lists, templates, scoring, subscriptions, market signals, summaries, credits, connectors, and organization context.
```bash
saber signal --domain acme.com --question "Are they hiring engineers?"
saber list company count-preview --industry "software development" --country US
saber template create --name "CRM" --question "Which CRM are they using?" --answer-type list
saber scoring scores --type company --object acme.com --detailed
```
## What you can do [#what-you-can-do]
## Command families [#command-families]
| Family | Purpose |
| ----------------------- | ------------------------------------------------------------------- |
| `auth` | Log in, check status, and log out. |
| `signal` | Create, list, fetch, and batch company or contact signal research. |
| `list company` | Create, preview, import, update, and browse company lists. |
| `list contact` | Create, search, update, and browse contact lists. |
| `template` | Manage reusable signal templates. |
| `subscription` | Schedule signal runs against company lists. |
| `market-signal` | Manage recurring market signal subscriptions. |
| `summary` | Generate and list AI summaries for a company domain. |
| `scoring` | Manage profiles, rules, assignments, and scores. |
| `org` | Read and update organization profile context for agent workflows. |
| `connectors`, `credits` | Inspect connector status and credit balance. |
| `init-claude` | Write Saber context into `CLAUDE.md` and help install Saber Skills. |
| `update`, `version` | Check for CLI updates and print version information. |
## Skills quick reference [#skills-quick-reference]
Saber Skills pair well with the CLI. Use a skill when you want the agent to plan and execute a GTM workflow, then let the CLI handle repeatable Saber operations such as signals, lists, scoring, and CRM sync.
| Agent task | Start with | CLI commands it may use |
| ------------------------------ | ---------------------------------------------------- | -------------------------------------------------- |
| Define ICP and buying signals | `$signal-discovery` | `saber org get`, `saber template create ...` |
| Build and enrich a target list | `$build-account-list`, `$build-contact-list` | `saber list company ...`, `saber list contact ...` |
| Activate signal tracking | `$create-company-signals`, `$create-contact-signals` | `saber signal ...`, `saber credits` |
| Prioritise accounts | `$score-accounts` | `saber scoring ...` |
| Sync CRM context | `$import-from-hubspot`, `$sync-to-hubspot` | `saber connectors`, list and signal commands |
See [Skills and CLI](/skills/cli) for the full handoff model.
## Next steps [#next-steps]
# Installation (/cli/installation)
## Install with curl [#install-with-curl]
```bash
curl -sSL https://raw.githubusercontent.com/saberapp/cli/main/scripts/install.sh | sh
```
The installer places `saber` on your `PATH` and prompts for elevated permissions only when needed.
## Install with Go [#install-with-go]
```bash
go install github.com/saberapp/cli/cmd/saber@latest
```
If `saber` is not found after installation, add Go's binary directory to your shell profile:
```bash
export PATH="$HOME/go/bin:$PATH"
```
## Verify [#verify]
```bash
saber version
saber help
```
## Update [#update]
Run the installer again to fetch the latest release, or reinstall with `go install`:
```bash
curl -sSL https://raw.githubusercontent.com/saberapp/cli/main/scripts/install.sh | sh
```
# Troubleshooting (/cli/troubleshooting)
## `saber` is not found [#saber-is-not-found]
Make sure the install directory is on your `PATH`.
```bash
export PATH="$HOME/go/bin:$PATH"
```
If you installed with the curl installer, open a new terminal session and run:
```bash
saber version
```
## Not authenticated [#not-authenticated]
Run:
```bash
saber auth login
saber auth status
```
If `auth status` still fails, create a fresh API key in Saber and log in again.
## A signal times out [#a-signal-times-out]
Use async mode and poll later:
```bash
saber signal -d acme.com -q "What changed recently?" --no-wait
saber signal get
```
For larger runs, use `saber signal batch --async` and process results by ID or webhook.
## A list would cost too many credits [#a-list-would-cost-too-many-credits]
Use `count-preview` before `create`, especially with technology or broad geography filters.
```bash
saber list company count-preview --technology hubspot --country US --size "51-200"
```
## JSON output is easier to parse [#json-output-is-easier-to-parse]
Add `--json` when a script, CI job, or agent needs stable machine-readable output.
```bash
saber credits --json
saber signal get --json
```
# Workflows (/cli/workflows)
## Research one account [#research-one-account]
```bash
saber signal -d acme.com -q "What does this company sell?"
saber signal -d acme.com -q "Are they hiring account executives?" --answer-type boolean
saber summary generate --domain acme.com
```
Use `summary generate` after several completed signals to turn individual answers into a call brief.
## Build a company list safely [#build-a-company-list-safely]
Preview before creating expensive technographic lists:
```bash
saber list company count-preview --technology hubspot --country US --size "51-200"
```
Then create:
```bash
saber list company create --name "HubSpot mid-market US" --technology hubspot --country US --size "51-200"
```
Export the list when a spreadsheet, review queue, or enrichment system needs a copy:
```bash
saber list company export --output hubspot-mid-market-us.csv
```
## Define reusable research [#define-reusable-research]
```bash
saber template create --name "Uses Salesforce" \
--question "Does this company use Salesforce as its CRM?" \
--answer-type boolean \
--weight important
```
Use the template for single-domain or batch research:
```bash
saber signal -d acme.com --template
saber signal batch --domain acme.com --domain stripe.com --template
```
For larger account sets, use async batch mode:
```bash
saber signal batch --domain acme.com --domain stripe.com --template --async
```
## Score accounts [#score-accounts]
```bash
saber scoring profile create --type company --name "Enterprise ICP"
saber scoring rule upsert --signal-template --dimension fit --answer-type boolean --true 10 --false 0
saber scoring assignment bulk --profile --type company --object acme.com --object stripe.com
saber scoring scores --type company --object acme.com --object stripe.com --detailed
```
## Schedule recurring signals [#schedule-recurring-signals]
```bash
saber subscription create \
--list \
--template \
--frequency weekly \
--timezone Europe/Amsterdam
saber subscription trigger
saber subscription list
```
# Companies (/companies)
Company workflows help you move from market exploration to actionable account intelligence. Use them to preview target segments, save reusable company lists, and run AI enrichments against companies before routing results into sales or CRM workflows.
## Recommended path [#recommended-path]
1. Start with [Search Companies](/companies/search) to validate a target segment.
2. Save the segment as a [Company List](/companies/lists) when the audience should be reused.
3. Run a [prebuilt enrichment](/enrichments), or design a [custom enrichment](/enrichments/custom) for a question they don't cover, to answer qualification, timing, and personalization questions.
4. Use scoring or CRM sync once the account set has enough enrichment coverage.
## Workflows [#workflows]
## Reference [#reference]
# Company Lists (/companies/lists)
## Overview [#overview]
Company Lists let you save reusable company segments, inspect list metadata, and page through the companies that matched the list filter.
Use lists when a segment needs to survive beyond a single preview request. Lists are the right boundary for review queues, CRM sync, scoring, enrichment activation, and repeatable agent workflows.
## Recommended workflow [#recommended-workflow]
1. Preview the target segment with [company search](/companies/search).
2. Create a named Company List from the validated filter.
3. Page through the companies in the list for review or export.
4. Run company enrichments or scoring against the list.
5. Update or delete the list when the underlying targeting changes.
## When to use lists [#when-to-use-lists]
Use a Company List when:
* The same segment will be used by multiple workflows.
* Sales or marketing teams need a stable working set.
* You want to sync companies into a CRM or enrichment table.
* You need to export or page through more than a preview result.
Use search only when you are testing a filter and do not need durable results yet.
## List hygiene [#list-hygiene]
Keep list names specific and workflow-oriented. A name like `US fintech Series B hiring sales leaders` is easier to audit than `new leads`.
When filters change materially, prefer creating a new list over mutating an existing production list. That keeps downstream analysis and CRM sync runs easier to reason about.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Search Companies (/companies/search)
## Overview [#overview]
Use company search when you want to preview a market segment before saving it as a list, running enrichment research, or syncing accounts into a downstream tool.
Search returns a bounded preview of companies that match a filter. Treat it as an exploration step, not as durable storage. If the segment is useful, create a Company List from the same filter so the results can be reviewed, paged, exported, or reused by workflows.
## Recommended workflow [#recommended-workflow]
1. Start with the narrowest filter that describes the target segment.
2. Run a search preview and inspect the first results for fit.
3. Adjust the filter until the preview matches the intended audience.
4. Create a Company List when you need to reuse the segment.
5. Run enrichments, scoring, or CRM sync against the saved list.
## Filter design [#filter-design]
Good filters are explicit enough to be repeatable. Prefer structured criteria such as industry, company size, geography, keywords, and technology signals over broad free-text descriptions.
Use a search preview before creating a list when:
* You are exploring a new ICP segment.
* You want to estimate whether a filter is too broad or too narrow.
* You need a small sample before spending credits on list creation.
* You are testing filters for an automated workflow.
## Operational notes [#operational-notes]
* Search previews are capped, so they are best for validation rather than exhaustive retrieval.
* Save the filter as a Company List before handing results to another system.
* Use stable filter definitions in CI or agent workflows so repeated runs are comparable.
* Check rate limits before running many previews in a loop.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Connectors (/connectors)
Connectors are integrations that link your CRM and sales tools to Saber, enabling enriched research and automated data syncing. Use the connectors API to verify which integrations are active before executing workflows.
**Supported connectors**
* **LinkedIn Sales Navigator** (`salesNavigator`) — used for contact search and profile enrichment
* **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace integration
## Connector status [#connector-status]
Check connector status before running workflows that depend on CRM or Sales Navigator data.
Connector checks are especially important before automated jobs. A missing or expired connection can cause search, enrichment, import, or sync workflows to fail even when the API key itself is valid.
## Recommended workflow [#recommended-workflow]
1. Check connector status at workflow startup.
2. Validate that the required connector is active for the operation.
3. Run the dependent workflow only after connector health is confirmed.
4. Surface connector failures separately from validation or rate-limit errors.
## When to check connectors [#when-to-check-connectors]
Check connectors before:
* Searching contacts through Sales Navigator.
* Importing company lists from HubSpot.
* Syncing enrichment results into CRM fields.
* Running scheduled jobs that depend on external data.
## Error handling [#error-handling]
Treat connector errors as operational issues, not bad input. Retrying immediately usually does not help if a user needs to reconnect an account or reinstall an app.
For user-facing workflows, show the missing connector and a direct next step. For background jobs, log the connector state with the failed run so the issue can be diagnosed without replaying the request.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Contact Enrichments (/contacts/enrichments)
## Overview [#overview]
Contact enrichments answer structured research questions about a person, usually from a LinkedIn profile or enriched contact record. Run one with `POST /v1/contacts/enrich/custom`.
Use contact enrichments when account-level research is not enough. They are best for persona research, outreach personalization, qualification, and understanding a contact's likely priorities.
## Running an enrichment [#running-an-enrichment]
Each call returns an [enrichment run](/enrichments#the-enrichment-run-envelope): a fresh run returns `202` with a `Location: /v1/enrichment-runs/{runId}` header to poll, a recent identical request returns the completed run inline as `200`, and you can pass a `webhookUrl` to have the completed run delivered.
## Enrichment design [#enrichment-design]
Good contact enrichments focus on evidence that helps a seller decide what to say or whether to prioritize a person:
* Current role and likely responsibilities.
* Relevant career history.
* Topics or initiatives mentioned publicly.
* Fit against a target persona.
* Outreach angle or objection risk.
Avoid asking for broad biographies when the workflow only needs one decision. Structured questions produce cleaner fields for CRM sync and scoring.
## Inputs and quality [#inputs-and-quality]
Contact enrichments work best with a stable profile URL or enriched contact record. If the contact identity is ambiguous, search or enrich the contact first, then run the enrichment against the resolved profile.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Contacts (/contacts)
Contact workflows help you turn target accounts into the right people to research and engage. Use them after company targeting is clear, especially when you need buying committee discovery, persona research, or outreach personalization.
## Recommended path [#recommended-path]
1. Confirm the required [connector](/connectors) is active.
2. Use [Search Contacts](/contacts/search) to find people at target accounts.
3. Save useful matches as a [Contact List](/contacts/lists).
4. Run [Contact Enrichments](/contacts/enrichments) to enrich context before outreach or CRM sync.
## Workflows [#workflows]
## Reference [#reference]
# Sales Navigator industry values (/contacts/industry-values)
Use these LinkedIn Sales Navigator labels in the `industries` field for `POST /v1/contacts/search`. Values inside the field use OR. Other filter groups use AND.
The API ignores surrounding whitespace and letter case, then sends the canonical label shown below. It validates labels on the server. The OpenAPI tool schema omits this large list to keep MCP discovery responses small.
LinkedIn replaced several familiar industry names. Common replacements are:
| Older name | Current name |
| ----------------------------------- | ------------------------------------ |
| Computer Software | Software Development |
| Internet | Technology, Information and Internet |
| Information Technology and Services | IT Services and IT Consulting |
## A [#a]
* Abrasives and Nonmetallic Minerals Manufacturing
* Accessible Architecture and Design
* Accessible Hardware Manufacturing
* Accommodation and Food Services
* Accounting
* Administration of Justice
* Administrative and Support Services
* Advertising Services
* Agricultural Chemical Manufacturing
* Agriculture, Construction, Mining Machinery Manufacturing
* Air, Water, and Waste Program Management
* Airlines and Aviation
* Alternative Dispute Resolution
* Alternative Fuel Vehicle Manufacturing
* Alternative Medicine
* Ambulance Services
* Amusement Parks and Arcades
* Animal Feed Manufacturing
* Animation and Post-production
* Apparel Manufacturing
* Appliances, Electrical, and Electronics Manufacturing
* Architectural and Structural Metal Manufacturing
* Architecture and Planning
* Armed Forces
* Artificial Rubber and Synthetic Fiber Manufacturing
* Artists and Writers
* Audio and Video Equipment Manufacturing
* Automation Machinery Manufacturing
* Aviation and Aerospace Component Manufacturing
## B [#b]
* Baked Goods Manufacturing
* Banking
* Bars, Taverns, and Nightclubs
* Bed-and-Breakfasts, Hostels, Homestays
* Beverage Manufacturing
* Biomass Electric Power Generation
* Biotechnology Research
* Blockchain Services
* Blogs
* Boilers, Tanks, and Shipping Container Manufacturing
* Book and Periodical Publishing
* Book Publishing
* Breweries
* Broadcast Media Production and Distribution
* Building Construction
* Building Equipment Contractors
* Building Finishing Contractors
* Building Structure and Exterior Contractors
* Business Consulting and Services
* Business Content
* Business Intelligence Platforms
## C [#c]
* Cable and Satellite Programming
* Capital Markets
* Caterers
* Chemical Manufacturing
* Chemical Raw Materials Manufacturing
* Child Day Care Services
* Chiropractors
* Circuses and Magic Shows
* Civic and Social Organizations
* Civil Engineering
* Claims Adjusting, Actuarial Services
* Clay and Refractory Products Manufacturing
* Climate Data and Analytics
* Climate Technology Product Manufacturing
* Coal Mining
* Collection Agencies
* Commercial and Industrial Equipment Rental
* Commercial and Industrial Machinery Maintenance
* Commercial and Service Industry Machinery Manufacturing
* Communications Equipment Manufacturing
* Community Development and Urban Planning
* Community Services
* Computer and Network Security
* Computer Games
* Computer Hardware Manufacturing
* Computer Networking Products
* Computers and Electronics Manufacturing
* Conservation Programs
* Construction
* Construction Hardware Manufacturing
* Consumer Goods Rental
* Consumer Services
* Correctional Institutions
* Cosmetology and Barber Schools
* Courts of Law
* Credit Intermediation
* Cutlery and Handtool Manufacturing
## D [#d]
* Dairy Product Manufacturing
* Dance Companies
* Data Infrastructure and Analytics
* Data Security Software Products
* Death Care Services
* Defense and Space Manufacturing
* Dentists
* Design Services
* Desktop Computing Software Products
* Digital Accessibility Services
* Distilleries
## E [#e]
* E-Learning Providers
* Economic Programs
* Education
* Education Administration Programs
* Electric Lighting Equipment Manufacturing
* Electric Power Generation
* Electric Power Transmission, Control, and Distribution
* Electrical Equipment Manufacturing
* Electronic and Precision Equipment Maintenance
* Embedded Software Products
* Emergency and Relief Services
* Energy Technology
* Engineering Services
* Engines and Power Transmission Equipment Manufacturing
* Entertainment Providers
* Environmental Quality Programs
* Environmental Services
* Equipment Rental Services
* Events Services
* Executive Offices
* Executive Search Services
## F [#f]
* Fabricated Metal Products
* Facilities Services
* Family Planning Centers
* Farming
* Farming, Ranching, Forestry
* Fashion Accessories Manufacturing
* Financial Services
* Fine Arts Schools
* Fire Protection
* Fisheries
* Flight Training
* Food and Beverage Manufacturing
* Food and Beverage Retail
* Food and Beverage Services
* Footwear and Leather Goods Repair
* Footwear Manufacturing
* Forestry and Logging
* Fossil Fuel Electric Power Generation
* Freight and Package Transportation
* Fruit and Vegetable Preserves Manufacturing
* Fuel Cell Manufacturing
* Fundraising
* Funds and Trusts
* Funeral Services
* Furniture and Home Furnishings Manufacturing
## G [#g]
* Gambling Facilities and Casinos
* Geothermal Electric Power Generation
* Glass Product Manufacturing
* Glass, Ceramics and Concrete Manufacturing
* Golf Courses and Country Clubs
* Government Administration
* Government Relations Services
* Graphic Design
* Ground Passenger Transportation
## H [#h]
* Health and Human Services
* Higher Education
* Highway, Street, and Bridge Construction
* Historical Sites
* Holding Companies
* Home Health Care Services
* Horticulture
* Hospitality
* Hospitals
* Hospitals and Health Care
* Hotels and Motels
* Household and Institutional Furniture Manufacturing
* Household Appliance Manufacturing
* Household Services
* Housing and Community Development
* Housing Programs
* Human Resources Services
* HVAC and Refrigeration Equipment Manufacturing
* Hydroelectric Power Generation
## I [#i]
* Individual and Family Services
* Industrial Machinery Manufacturing
* Industry Associations
* Information Services
* Insurance
* Insurance Agencies and Brokerages
* Insurance and Employee Benefit Funds
* Insurance Carriers
* Interior Design
* International Affairs
* International Trade and Development
* Internet Marketplace Platforms
* Internet News
* Internet Publishing
* Interurban and Rural Bus Services
* Investment Advice
* Investment Banking
* Investment Management
* IT Services and IT Consulting
* IT System Custom Software Development
* IT System Data Services
* IT System Design Services
* IT System Installation and Disposal
* IT System Operations and Maintenance
* IT System Testing and Evaluation
* IT System Training and Support
## J [#j]
* Janitorial Services
## L [#l]
* Landscaping Services
* Language Schools
* Laundry and Drycleaning Services
* Law Enforcement
* Law Practice
* Leasing Non-residential Real Estate
* Leasing Residential Real Estate
* Leather Product Manufacturing
* Legal Services
* Legislative Offices
* Libraries
* Lime and Gypsum Products Manufacturing
* Loan Brokers
## M [#m]
* Machinery Manufacturing
* Magnetic and Optical Media Manufacturing
* Manufacturing
* Maritime Transportation
* Market Research
* Marketing Services
* Mattress and Blinds Manufacturing
* Measuring and Control Instrument Manufacturing
* Meat Products Manufacturing
* Media and Telecommunications
* Media Production
* Medical and Diagnostic Laboratories
* Medical Equipment Manufacturing
* Medical Practices
* Mental Health Care
* Metal Ore Mining
* Metal Treatments
* Metal Valve, Ball, and Roller Manufacturing
* Metalworking Machinery Manufacturing
* Military and International Affairs
* Mining
* Mobile Computing Software Products
* Mobile Food Services
* Mobile Gaming Apps
* Motor Vehicle Manufacturing
* Motor Vehicle Parts Manufacturing
* Movies and Sound Recording
* Movies, Videos, and Sound
* Museums
* Museums, Historical Sites, and Zoos
* Musicians
## N [#n]
* Nanotechnology Research
* Natural Gas Distribution
* Natural Gas Extraction
* Newspaper Publishing
* Non-profit Organizations
* Nonmetallic Mineral Mining
* Nonresidential Building Construction
* Nuclear Electric Power Generation
* Nursing Homes and Residential Care Facilities
## O [#o]
* Office Administration
* Office Furniture and Fixtures Manufacturing
* Oil and Coal Product Manufacturing
* Oil and Gas
* Oil Extraction
* Oil, Gas, and Mining
* Online and Mail Order Retail
* Online Audio and Video Media
* Operations Consulting
* Optometrists
* Outpatient Care Centers
* Outsourcing and Offshoring Consulting
## P [#p]
* Packaging and Containers Manufacturing
* Paint, Coating, and Adhesive Manufacturing
* Paper and Forest Product Manufacturing
* Pension Funds
* Performing Arts
* Performing Arts and Spectator Sports
* Periodical Publishing
* Personal and Laundry Services
* Personal Care Product Manufacturing
* Personal Care Services
* Pet Services
* Pharmaceutical Manufacturing
* Philanthropic Fundraising Services
* Photography
* Physical, Occupational and Speech Therapists
* Physicians
* Pipeline Transportation
* Plastics and Rubber Product Manufacturing
* Plastics Manufacturing
* Political Organizations
* Postal Services
* Primary and Secondary Education
* Primary Metal Manufacturing
* Printing Services
* Professional Organizations
* Professional Services
* Professional Training and Coaching
* Public Assistance Programs
* Public Health
* Public Policy Offices
* Public Relations and Communications Services
* Public Safety
## R [#r]
* Racetracks
* Radio and Television Broadcasting
* Rail Transportation
* Railroad Equipment Manufacturing
* Ranching
* Ranching and Fisheries
* Real Estate
* Real Estate Agents and Brokers
* Real Estate and Equipment Rental Services
* Recreational Facilities
* Regenerative Design
* Religious Institutions
* Renewable Energy Equipment Manufacturing
* Renewable Energy Power Generation
* Repair and Maintenance
* Research Services
* Residential Building Construction
* Restaurants
* Retail
* Retail Apparel and Fashion
* Retail Appliances, Electrical, and Electronic Equipment
* Retail Art Dealers
* Retail Art Supplies
* Retail Books and Printed News
* Retail Building Materials and Garden Equipment
* Retail Florists
* Retail Furniture and Home Furnishings
* Retail Gasoline
* Retail Groceries
* Retail Health and Personal Care Products
* Retail Luxury Goods and Jewelry
* Retail Motor Vehicles
* Retail Musical Instruments
* Retail Office Equipment
* Retail Office Supplies and Gifts
* Retail Pharmacies
* Retail Recyclable Materials & Used Merchandise
* Reupholstery and Furniture Repair
* Robot Manufacturing
* Robotics Engineering
* Rubber Products Manufacturing
## S [#s]
* Satellite Telecommunications
* Savings Institutions
* School and Employee Bus Services
* Seafood Product Manufacturing
* Secretarial Schools
* Securities and Commodity Exchanges
* Security and Investigations
* Security Guards and Patrol Services
* Security Systems Services
* Semiconductor Manufacturing
* Services for Renewable Energy
* Services for the Elderly and Disabled
* Sheet Music Publishing
* Shipbuilding
* Shuttles and Special Needs Transportation Services
* Sightseeing Transportation
* Skiing Facilities
* Smart Meter Manufacturing
* Soap and Cleaning Product Manufacturing
* Social Networking Platforms
* Software Development
* Solar Electric Power Generation
* Sound Recording
* Space Research and Technology
* Specialty Trade Contractors
* Spectator Sports
* Sporting Goods Manufacturing
* Sports and Recreation Instruction
* Sports Teams and Clubs
* Spring and Wire Product Manufacturing
* Staffing and Recruiting
* Steam and Air-Conditioning Supply
* Strategic Management Services
* Subdivision of Land
* Sugar and Confectionery Product Manufacturing
* Surveying and Mapping Services
## T [#t]
* Taxi and Limousine Services
* Technical and Vocational Training
* Technology, Information and Internet
* Technology, Information and Media
* Telecommunications
* Telecommunications Carriers
* Telephone Call Centers
* Temporary Help Services
* Textile Manufacturing
* Theater Companies
* Think Tanks
* Tobacco Manufacturing
* Translation and Localization
* Transportation Equipment Manufacturing
* Transportation Programs
* Transportation, Logistics, Supply Chain and Storage
* Travel Arrangements
* Truck Transportation
* Trusts and Estates
* Turned Products and Fastener Manufacturing
## U [#u]
* Urban Transit Services
* Utilities
* Utilities Administration
* Utility System Construction
## V [#v]
* Vehicle Repair and Maintenance
* Venture Capital and Private Equity Principals
* Veterinary Services
* Vocational Rehabilitation Services
## W [#w]
* Warehousing and Storage
* Waste Collection
* Waste Treatment and Disposal
* Water Supply and Irrigation Systems
* Water, Waste, Steam, and Air Conditioning Services
* Wellness and Fitness Services
* Wholesale
* Wholesale Alcoholic Beverages
* Wholesale Apparel and Sewing Supplies
* Wholesale Appliances, Electrical, and Electronics
* Wholesale Building Materials
* Wholesale Chemical and Allied Products
* Wholesale Computer Equipment
* Wholesale Drugs and Sundries
* Wholesale Food and Beverage
* Wholesale Footwear
* Wholesale Furniture and Home Furnishings
* Wholesale Hardware, Plumbing, Heating Equipment
* Wholesale Import and Export
* Wholesale Luxury Goods and Jewelry
* Wholesale Machinery
* Wholesale Metals and Minerals
* Wholesale Motor Vehicles and Parts
* Wholesale Paper Products
* Wholesale Petroleum and Petroleum Products
* Wholesale Photography Equipment and Supplies
* Wholesale Raw Farm Products
* Wholesale Recyclable Materials
* Wind Electric Power Generation
* Wineries
* Wireless Services
* Women's Handbag Manufacturing
* Wood Product Manufacturing
* Writing and Editing
## Z [#z]
* Zoos and Botanical Gardens
# Contact Lists (/contacts/lists)
## Overview [#overview]
Contact Lists store snapshots of matched contacts so your team can reuse a researched audience without rerunning the source search each time.
Use Contact Lists for prospecting queues, campaign audiences, enrichment workflows, and review steps before outreach. A list gives teams a stable set of people to inspect, enrich, and sync.
## Recommended workflow [#recommended-workflow]
1. Identify target accounts with company search or imported CRM data.
2. Run contact search against those accounts.
3. Create a Contact List from the validated search.
4. Page through contacts for QA or enrichment.
5. Run contact enrichments before outreach or CRM sync.
## Snapshot behavior [#snapshot-behavior]
Contact Lists are point-in-time snapshots. That makes them useful for repeatable workflows because the working set does not change every time a source system changes.
Create a new list when you want to compare a new search strategy against an older one. Update an existing list when the working set should be treated as the same operational audience.
## List hygiene [#list-hygiene]
Use names that capture audience, source, and intent. For example, `Enterprise security VP prospects from target accounts` is easier to route than `security contacts`.
Track which workflow owns each list. Lists used by automated enrichment or CRM sync should have stricter review rules than lists used for ad hoc research.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Search Contacts (/contacts/search)
## Overview [#overview]
Use contact search to find people at target accounts through connected sources such as LinkedIn Sales Navigator.
You can search named accounts or find whitespace prospects. Use role filters to define the person. Use industry, company headcount, and company type to define the market.
## Recommended workflow [#recommended-workflow]
1. Confirm that the Sales Navigator connector is active.
2. Define the audience with company, role, tenure, and firmographic filters.
3. Review the returned role tenure and company tenure.
4. Save useful results into a Contact List.
5. Run contact enrichments before outreach.
## Search strategy [#search-strategy]
For named-account prospecting, pass `companyLinkedInUrls`. For whitespace prospecting, omit company URLs and combine `industries`, `companyHeadcounts`, and `companyTypes`.
Use an exact value from the [Sales Navigator industry reference](/contacts/industry-values). The API rejects unknown industry labels.
Good contact search filters usually include:
* Target companies or firmographic criteria.
* Role or department criteria.
* Seniority constraints.
* Geography or region when territory matters.
* Tenure criteria when timing matters.
## Recent movers [#recent-movers]
Set `recentlyChangedJobs: true` to use Sales Navigator's Changed jobs filter. LinkedIn controls its rolling window and provides no caller-selected start date. Approximately 90 days is an unverified working assumption.
You can also use `yearsInCurrentPosition` to define a wider or narrower candidate pool. Run separate searches with Changed jobs, short role tenure, and both filters if you need to compare their coverage.
Each returned position includes:
* `tenureAtPosition`: Time in this current role.
* `tenureAtCompany`: Total time at this employer across current and earlier roles.
* `tenureInMonths`: the existing collapsed value, kept for compatibility.
* `moveType`: our classification of the position. See below.
The two tenure values are returned independently, so you can compare them without a loss of supplier data.
## Move classification [#move-classification]
`moveType` tells you if a position looks like a recent move, and which kind. It has four values.
| Value | Meaning |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| `moved_internally` | A new role at an employer the person already worked for. |
| `joined_new_employer` | The tenure values do not show earlier time at this employer. This can also be a missed internal move. |
| `no_recent_move` | The person has held the role for more than 12 months. |
| `unknown` | The tenure data cannot show a direction. |
The classification applies to one position, not to the person. A person can hold more than one current position, and each position gets its own value.
## People with several current positions [#people-with-several-current-positions]
Some people hold several current roles at once. For example, they can have a main job and a board or advisory role.
The top-level `role` and `companyName` fields summarize the current position with the latest `startedOn` date. A start date with a year but no month counts as January. A dated position wins over an undated position. If dates are equal, or every date is absent, the first position in the response wins.
The summary selection does not reorder or remove anything from `positions[]`. Use `positions[]` when you need every current position returned by the search.
The `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `seniorityLevels`, and `departments` filters match the person. They do not identify which current position matched. A person can match through one position while `role` and `companyName` summarize a different, newer position.
Sales Navigator can return a different set or order of current positions for the same person in different searches. When you combine `companyLinkedInUrls` and `jobTitles`, Saber can move the matching position to the front of `positions[]`. The top-level summary still names the position with the latest start date.
We calculate `moveType` from the two tenure values. A position is a recent move if the person has held the role for 12 months or less. It is an internal move if the company tenure is 1 month or more than the position tenure.
### How accurate it is [#how-accurate-it-is]
We measured the classification against 795 people, and compared 175 of them with their full work history.
* Where we gave `moved_internally`, it was correct in all 72 cases. It made no false claim.
* It finds approximately 2 of each 3 internal moves.
### Known limit [#known-limit]
Some people do not put an end date on their previous role at the same employer. Sales Navigator then shows only the new role, and decreases the company tenure to agree with the position tenure. That person gets `joined_new_employer`, although the person moved internally.
This is a limit of the supplier data. Do not read `joined_new_employer` as proof that the person came from a different employer.
### Not a promotion [#not-a-promotion]
`moveType` tells you that the person changed role. It does not tell you that the new role is more senior. We tested a comparison of job titles for this, and it was not reliable.
Sales Navigator does not provide sort control for this search. Keep your own deduplication key across repeated runs.
## Pagination [#pagination]
Each request returns one page. The default page size is 25 contacts. The maximum is 100.
Start with your search filters and a `limit`:
```json
{
"jobTitles": ["Engineering Manager"],
"countries": ["US"],
"limit": 25
}
```
If the response includes `nextCursor`, send a second request with that value. Repeat the same filters and `limit`:
```json
{
"jobTitles": ["Engineering Manager"],
"countries": ["US"],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
```
Continue until `nextCursor` is absent or `hasMore` is `false`. Do not calculate the number of pages from `total`. Only the first 1,000 results are available. Narrow the filters when you need results beyond that limit.
Use `cursor` for pagination. Saber keeps all pages in one pagination session on the same Sales Navigator connector. The older `offset` field remains available for compatibility, but it does not keep this connector continuity.
If that connector becomes unavailable, the API returns `422` with `error.errorCode: "PAGINATION_SOURCE_UNAVAILABLE"`. The same response can occur during a service update when an older service version cannot preserve connector continuity; in that case, `error.details.reason` is `"connector_affinity_unsupported"`. Restart the search without a cursor.
If the API returns `429`, wait for the number of seconds in the `Retry-After` response header. Then retry the same cursor with the same filters and `limit`.
An unexpected connector mismatch returns `500` with `error.code: "PAGINATION_CONNECTOR_AFFINITY_INVALID"`. Restart the search without a cursor. Contact support if the error repeats.
An invalid, expired, or older cursor returns `422` with `error.code: "INVALID_CURSOR"`. Restart the search without a cursor.
Each successful page costs 0.5 credits. Failed requests do not use Saber credits.
## Connector dependency [#connector-dependency]
Contact search depends on connected data sources. If a search returns connector-related errors, check the connector page before changing the filter.
If a request uses `departments` and LinkedIn reports dropping that filter, the API returns `422` with `error.errorCode: "CONTACT_SEARCH_FILTER_DROPPED"`. That request does not use Saber credits. Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Custom Enrichments (/enrichments/custom)
## Overview [#overview]
A custom question runs as `POST /v1/companies/enrich/custom` (or `/v1/contacts/enrich/custom`) and returns the `enrichment_run` envelope. The legacy `/v1/companies/signals[/sync|/batch]` routes keep working at the same 2-credit price until their announced sunset — responses carry `Deprecation` and successor `Link` headers.
A **custom enrichment** answers a research question you write yourself, in the answer shape you define. It's built for the questions that are specific to your business — the qualification criteria, product mentions, and buying-intent evidence that no prebuilt type covers.
If your question is one of the common ones — funding, M\&A, technology stack, open jobs, or firmographics — a [prebuilt enrichment](/enrichments) answers it from just a domain, with no question or answer type to design.
Each request creates an enrichment **run**. Keep the question precise and request the answer in the shape your workflow needs: smaller, structured questions produce outputs that are easier to score, compare, and sync into CRM fields.
## Choosing an execution mode [#choosing-an-execution-mode]
`POST /v1/companies/enrich/custom` returns the `enrichment_run` envelope. How you collect the result depends on the mode:
* **Run and poll (default).** A fresh run returns `202 Accepted` with `Location: /v1/enrichment-runs/{runId}`; read the run on `GET /v1/enrichment-runs/{runId}` until it completes. A cache serve returns `200 OK` inline (free). Best when you can collect the answer a moment later, or an agent is driving the request.
* **Run and receive a webhook.** Pass a `webhookUrl` and the completed result is delivered to it — no polling. Best for decoupling submission from processing.
* **Batch.** `POST /v1/companies/enrich/custom/batch` runs the same question across many domains for list-level analysis; collect results in bulk.
Need the answer in the same response? The legacy `POST /v1/companies/signals/sync` route still blocks until completion (and honors a request timeout header) until its sunset. On the new surface, prefer a run plus a `webhookUrl` or a short poll — the run model is what batch, monitors, and the unified `GET /v1/enrichment-runs` reads all build on.
## Writing a good question [#writing-a-good-question]
Strong questions are specific, observable, and tied to an action. Instead of asking whether a company is a good fit, ask about concrete evidence:
* Whether the company is hiring for a relevant function.
* Which products or business lines are mentioned on the site.
* Whether the company uses or mentions a target technology.
* Which recent events suggest expansion, churn risk, or buying intent.
Pick an `answerType` that matches downstream usage. Use `boolean` for routing, `list` for evidence collection, `number` or `percentage` for scoring inputs, and `open_text` for human-readable context.
## Operational notes [#operational-notes]
* Prefer a run plus a `webhookUrl` (or a short poll of `GET /v1/enrichment-runs/{runId}`) for agent and CLI workflows.
* Store the **run ID** from the `202`'s `Location` header so you can read the result later.
* Repeat calls for the same answer are served from the cache for free; only fresh runs are charged.
* Design batch jobs around reusable [enrichment templates](/api/enrichment-templates) when running large repeated research tasks — or hand the template to a [watch monitor](/monitors) to re-run it on a schedule.
## Related workflows [#related-workflows]
## Relevant API reference [#relevant-api-reference]
# Firmographics (/enrichments/firmographics)
## What it answers [#what-it-answers]
The Firmographics signal returns a company's base profile — headcount, size band, entity type, founded year, and HQ location — plus a **Saber-derived category** and the segments it sells to.
The differentiator is `saberCategory`. Public industry tags over-label: they classify many distinct companies broadly as "Software." This signal refines that coarse tag into a generalized→niche category derived from the company's own site and product pages, with cited evidence. For example, it turns a generic "Software" label into "Construction Software" with a niche of "Construction ERP / project-cost management."
**Key:** `firmographics` · **Scope:** company · **Cost:** 0.6 credits per fresh result (cache serves and failures are free)
The legacy routes (`POST /v1/signals/firmographics` and `POST /v1/signals/sync/firmographics`) keep working at their old 2-credit price until their announced sunset — responses carry `Deprecation` and successor `Link` headers. New integrations should use the endpoint above.
## Priors vs researched fields [#priors-vs-researched-fields]
This signal is explicit about which fields are copied from authoritative enrichment and which are researched:
* **Priors, copied verbatim when known** — `employeeCount`, `linkedInFollowers`, `type`, `foundedYear`, and `website` come from Saber's enrichment data when present, and are researched only when that data is absent. They are omitted rather than guessed when they can't be confirmed.
* **Researched, evidence-cited** — `saberCategory` and `segments` are the signal's whole purpose. They are derived from the company's public web presence and carry a rationale and supporting sources. The raw public industry tag is echoed back in `linkedinIndustry` for transparency, so you can see the prior alongside the refined answer.
## Request [#request]
Provide only a domain.
```bash
curl -X POST https://api.saber.app/v1/companies/enrich/firmographics \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "procore.com"
}'
```
For webhook delivery, call the same endpoint with a `webhookUrl` to have the completed result delivered.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `domain` | string | yes | Company domain to research. |
| `webhookUrl` | string | no | Where to deliver the completed result when the run finishes (`202` path). On a `200` cache serve the answer is already in the response. |
| `forceRefresh` | boolean | no | Bypass the cache and force a fresh run. |
## Response [#response]
A completed run's `result` conforms to the firmographics answer schema:
(The legacy `/v1/signals` routes return the same payload under `answer.jsonSchema`.)
```json
{
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": { "city": "Carpinteria", "state": "CA", "countryCode": "US" },
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{ "type": "company_site", "url": "https://www.procore.com/platform", "title": "Procore Platform Overview" }
]
},
"segments": ["B2B"],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
```
| Field | Type | Description |
| ----------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `domain` | string | Primary web domain — the identity anchor for the company. |
| `name`, `website`, `employeeCount`, `sizeRange`, `type`, `foundedYear`, `headquarters`, `linkedInFollowers` | mixed | Base profile fields. The authoritative priors — `employeeCount`, `linkedInFollowers`, `type`, `foundedYear`, `website` — are copied verbatim when known; the rest are researched. All are omitted rather than guessed when unconfirmed. |
| `linkedinIndustry` | string | The raw coarse public industry tag, echoed for transparency (the prior, not the answer). |
| `saberCategory` | object | The refined classification: `primary`, `niche`, `confidence`, `rationale`, and cited `evidence`. |
| `segments` | array | Who the company sells to, ordered by dominance: `B2B`, `B2C`, `B2B2C`, `B2G`, `C2C`. |
| `segmentRationale` | string | One line justifying the segments, especially for multi-value or `B2B2C` cases. |
`domain`, `saberCategory`, and `segments` are always present; the base profile fields appear when confirmed.
# Funding (/enrichments/funding)
## What it answers [#what-it-answers]
The Funding signal summarises a company's most recent funding and its public status. It reports the latest **primary** equity round — never a secondary sale, tender offer, or debt event — and flags whether the company has gone public. A completed IPO, direct listing, or SPAC merger sets `isPublic` to `true` and `latestRound` to `IPO`, overriding earlier private rounds.
Answers favour reliability over completeness: the AI confirms that sources belong to the company at the domain you passed (not a similarly-named company) and returns the one to three most recent events rather than a full history.
**Key:** `funding` · **Scope:** company · **Cost:** 0.3 credits per fresh result (cache serves and failures are free)
The legacy routes (`POST /v1/signals/funding` and `POST /v1/signals/sync/funding`) keep working at their old 2-credit price until their announced sunset — responses carry `Deprecation` and successor `Link` headers. New integrations should use the endpoint above.
## Request [#request]
Provide only a domain.
```bash
curl -X POST https://api.saber.app/v1/companies/enrich/funding \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "stripe.com"
}'
```
For webhook delivery, call the same endpoint with a `webhookUrl` to have the completed result delivered.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `domain` | string | yes | Company domain to research. |
| `webhookUrl` | string | no | Where to deliver the completed result when the run finishes (`202` path). On a `200` cache serve the answer is already in the response. |
| `forceRefresh` | boolean | no | Bypass the cache and force a fresh run. |
## Response [#response]
A completed run's `result` conforms to the funding answer schema:
(The legacy `/v1/signals` routes return the same payload under `answer.jsonSchema`.)
```json
{
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": ["Andreessen Horowitz", "Sequoia Capital", "General Catalyst"],
"announcedAt": "2023-03-15",
"sourceUrls": ["https://stripe.com/newsroom/news/series-i"]
}
]
}
```
| Field | Type | Description |
| ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hasFundingEvent` | boolean | True if any known funding event exists. |
| `isPublic` | boolean | True if the company has gone public (IPO / direct listing / SPAC). |
| `latestRound` | string | Most recent primary equity round or stage (e.g. `Series B`, `IPO`, `Unknown`). Never a secondary, tender, or debt event. |
| `latestEvents` | array | The latest one to three events, most recent first. Each carries `eventType`, optional `amount`/`currency`, `round`, `investors`, `announcedAt`, and `sourceUrls`. |
`hasFundingEvent` and `isPublic` are always present; the other fields appear when the evidence supports them.
## Evidence [#evidence]
Every reported event is corroborated against public sources (company newsroom or IR pages, regulatory filings, major business press), and the URLs are returned in `sourceUrls` and in the signal's top-level `sources`. The signal does not fetch paywalled database pages, and an amount is omitted rather than guessed when it isn't disclosed.
# Overview (/enrichments)
An **enrichment** answers a research question about a company or contact. Each run researches the live web, grounds its answer in public evidence (with source citations where the type provides them), and returns JSON you can score, route, or sync into a CRM without post-processing.
Five **prebuilt types** cover the questions teams ask most often: pass a company domain and get a structured answer with a documented schema. When your question isn't one of the five, a **[custom enrichment](/enrichments/custom)** lets you ask it in your own words with your own answer shape. `GET /v1/enrichment-types` returns this catalogue machine-readably — keys, schemas, prices, and execution modes.
## Available types [#available-types]
| Type | Key | Answers | Credits |
| ------------------------------------------- | --------------- | ----------------------------------------------------------------------- | ------- |
| [Funding](/enrichments/funding) | `funding` | Most recent funding round and public/IPO status | 0.3 |
| [M\&A](/enrichments/mna) | `mna` | Whether the company was acquired or made acquisitions | 0.75 |
| [Tech stack](/enrichments/tech-stack) | `tech-stack` | Which ERP/CRM it uses, or whether it uses a specific technology | 0.75 |
| [Open jobs](/enrichments/open-jobs) | `open-jobs` | What the company is currently hiring for | 0.5 |
| [Firmographics](/enrichments/firmographics) | `firmographics` | Base company profile plus a refined Saber category | 0.6 |
| [Custom](/enrichments/custom) | — | Your own research question, in the answer shape you define | 2 |
| Work email | `work-email` | A contact's verified work email (`POST /v1/contacts/enrich/work-email`) | 0.1 |
Prices are the standard rate card, charged per fresh result — repeat calls served from the cache are free, and failed runs are never charged. See [/pricing](/pricing) for the current card.
## How a run completes [#how-a-run-completes]
`POST /v1/companies/enrich/{type}` returns the `enrichment_run` envelope:
* **`202 Accepted`** with `Location: /v1/enrichment-runs/{runId}` — a fresh run started; poll the run for the result, or pass a `webhookUrl` to have the completed result delivered.
* **`200 OK`** inline — the answer was served from the cache (free).
`GET /v1/enrichment-runs` lists runs across both entity types with common filters.
## Migrating from the Signals API [#migrating-from-the-signals-api]
**The old "Signals API" is not the new "Signals".** The question-answering endpoints previously called *signals* are now **enrichments** — same contracts, same answer schemas, new names and per-type prices. The word **signal** now means something else: an *occurrence* emitted by a [monitor](/monitors) (a funding round detected, an answer that changed). If you see `signal` in the new surface, think "something happened", not "a research answer".
The legacy routes keep working **at their existing prices** until their announced sunset — the custom and prebuilt signal routes stay at **2 credits per answer**, while `find-email` stays at **0.1**, `summaries` stay **free**, and the subscription families keep their own watch and discovery rates. Their responses carry `Deprecation` and successor `Link` headers so tooling can discover the mapping:
| Legacy route | Successor |
| --------------------------------------------------------------- | ------------------------------------------ |
| `POST /v1/signals/{key}` (and `/sync/{key}`) | `POST /v1/companies/enrich/{type}` |
| `POST /v1/companies/signals[/sync\|/batch]` | `POST /v1/companies/enrich/custom[/batch]` |
| `POST /v1/contacts/signals[/sync]` | `POST /v1/contacts/enrich/custom` |
| `POST /v1/contacts/find-email` | `POST /v1/contacts/enrich/work-email` |
| `POST /v1/companies/signals/summaries` | `POST /v1/companies/summaries` |
| `/v1/companies/signals/subscriptions/*`, `/v1/market-signals/*` | [`/v1/monitors/*`](/monitors) |
One migration step per integration: swap the path, read the `enrichment_run` envelope, and enjoy the per-type price.
# M&A (/enrichments/mna)
## What it answers [#what-it-answers]
The M\&A signal detects mergers and acquisitions in both directions: the queried company being **acquired** (`role: acquiree`) and the queried company **acquiring** others (`role: acquirer`). It reports the counterparty, the deal status, and the value where disclosed.
Only true acquisitions — a transfer of control — count. Minority investments, funding rounds, and partnerships are excluded. The deal `status` distinguishes a closed deal from one that was merely announced or that later collapsed: an announced deal that is subsequently called off or blocked is `terminated`, never `completed`.
The window differs by direction: an acquisition of the company (`acquiree`) is a durable ownership fact reported at any age, while the company acquiring others (`acquirer`) is a recency signal limited to the last 24 months.
**Key:** `mna` · **Scope:** company · **Cost:** 0.75 credits per fresh result (cache serves and failures are free)
The legacy routes (`POST /v1/signals/mna` and `POST /v1/signals/sync/mna`) keep working at their old 2-credit price until their announced sunset — responses carry `Deprecation` and successor `Link` headers. New integrations should use the endpoint above.
## Request [#request]
Provide only a domain.
```bash
curl -X POST https://api.saber.app/v1/companies/enrich/mna \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "figma.com"
}'
```
For webhook delivery, call the same endpoint with a `webhookUrl` to have the completed result delivered.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `domain` | string | yes | Company domain to research. |
| `webhookUrl` | string | no | Where to deliver the completed result when the run finishes (`202` path). On a `200` cache serve the answer is already in the response. |
| `forceRefresh` | boolean | no | Bypass the cache and force a fresh run. |
## Response [#response]
A completed run's `result` conforms to the M\&A answer schema:
(The legacy `/v1/signals` routes return the same payload under `answer.jsonSchema`.)
```json
{
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": ["https://news.adobe.com/news/2023/12/adobe-figma-update"]
}
]
}
```
| Field | Type | Description |
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `detected` | boolean | True if at least one corroborated announced, completed, or terminated acquisition was found. A rumored-only finding does not count. |
| `latestEvents` | array | The one to three most relevant events, most recent first. Each carries `role` (`acquirer`/`acquiree`), `counterparty`, `status` (`rumored`/`announced`/`completed`/`terminated`), optional `amount`/`currency`, `announcedAt`, and `sourceUrls`. |
`detected` is always present; `latestEvents` appears when there is a qualifying event.
## Evidence [#evidence]
An acquisition backed **only** by a data aggregator, with no primary corroboration — acquirer or acquiree press/IR, a regulatory or securities filing, or major business press — does not meet the bar for `detected: true` or `status: completed`. Such a finding is treated as at most `rumored`, and `detected` stays `false` unless a primary source confirms the deal. Deal amounts are omitted rather than reported as zero when undisclosed.
# Open Jobs (/enrichments/open-jobs)
## What it answers [#what-it-answers]
The Open Jobs signal summarises a company's current hiring from public job postings, framed for market and competitive intelligence: what a company is building and investing in, rather than who to contact. It reports how many roles are open, the function areas and technologies behind them, and where the roles are.
**Key:** `open-jobs` · **Scope:** company · **Cost:** 0.5 credits per fresh result (cache serves and failures are free)
The legacy routes (`POST /v1/signals/open-jobs` and `POST /v1/signals/sync/open-jobs`) keep working at their old 2-credit price until their announced sunset — responses carry `Deprecation` and successor `Link` headers. New integrations should use the endpoint above.
## Counts vs examples [#counts-vs-examples]
The distinction that matters when reading this signal:
* `openings` is the **true total** of roles matching the trailing 90-day window. It comes from the job search's reported match count, not from the handful of listings the signal samples, so it is reliable even though the other fields are drawn from a small sample.
* `topTitles`, `hiringThemes`, `techHiringFor`, and `locations` are **examples** taken from that sample. They illustrate what and where the company is hiring — they are not a ranked or proportional breakdown, and you should not read percentages into them.
`hiringThemes` maps sampled roles onto a fixed function taxonomy (Engineering, Data/ML, Product, Sales/GTM, Marketing, Operations, Finance/Legal, People/HR, Support, Other), so the field stays filterable and stable across runs instead of drifting between synonyms.
## Request [#request]
Provide only a domain.
```bash
curl -X POST https://api.saber.app/v1/companies/enrich/open-jobs \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "datadoghq.com"
}'
```
For webhook delivery, call the same endpoint with a `webhookUrl` to have the completed result delivered.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `domain` | string | yes | Company domain to research. |
| `webhookUrl` | string | no | Where to deliver the completed result when the run finishes (`202` path). On a `200` cache serve the answer is already in the response. |
| `forceRefresh` | boolean | no | Bypass the cache and force a fresh run. |
## Response [#response]
A completed run's `result` conforms to the open-jobs answer schema:
(The legacy `/v1/signals` routes return the same payload under `answer.jsonSchema`.)
```json
{
"openings": 876,
"topTitles": ["Senior Software Engineer", "Product Manager", "Enterprise Account Executive"],
"hiringThemes": ["Engineering", "Product", "Sales/GTM"],
"techHiringFor": ["Go", "Kubernetes", "React"],
"locations": ["New York, NY", "Remote", "Paris, France"],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
```
| Field | Type | Description |
| ------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
| `openings` | integer | Total roles posted in the last 90 days — the true match count, not the sample size. `0` if none. |
| `topTitles` | array | Representative verbatim job titles from the sample. Examples, not exhaustive. |
| `hiringThemes` | array | Function areas from the fixed taxonomy. Examples of where the company is investing, not counted. |
| `techHiringFor` | array | Technologies named in the sampled postings (forward-looking hiring intent). Taken from the postings, never inferred. |
| `locations` | array | Representative locations (city/region/country or `Remote`). |
| `latestPostingDate` | string | Most recent posting date in the sample, `YYYY-MM-DD`. |
| `source` | string | Always `public_job_listings`. |
`openings`, `topTitles`, and `source` are always present; when `openings` is `0`, the example arrays are empty.
**Provenance.** Every field is derived from public job postings (`source` is always `public_job_listings`); this signal reports aggregate counts and sampled examples rather than per-posting source URLs.
## Relationship to the Tech signal [#relationship-to-the-tech-signal]
`techHiringFor` is the forward-looking complement to the [Tech signal](/enrichments/tech-stack): Tech reports the stack a company runs **today**, while `techHiringFor` reports the technologies named in what they are hiring for **now**. Values are drawn from the postings' structured tags, not guessed from general knowledge.
# Tech (/enrichments/tech-stack)
## What it answers [#what-it-answers]
The Tech signal detects which system a company **currently** uses in a technology category, or verifies whether it uses a specific product. It answers one of two scopes depending on which parameter you supply:
* **Category scope** (`category`) — "Which CRM system does this company use?" Returns every system detected in the category, with the current system of record in `primary`.
* **Technology scope** (`technology`) — "Does this company use Salesforce?" Returns a one-element `technologies` list whose `status` carries the verdict.
**Key:** `tech-stack` · **Scope:** company · **Cost:** 0.75 credits per fresh result (cache serves and failures are free)
The legacy routes (`POST /v1/signals/tech` and `POST /v1/signals/sync/tech`) keep working at their old 2-credit price until their announced sunset — responses carry `Deprecation` and successor `Link` headers. New integrations should use the endpoint above.
## Evidence over vendor data [#evidence-over-vendor-data]
The differentiator of this signal is *how* it decides. Third-party technographics data is treated as a **prior, not the answer**. Vendor detections enter the research loop only as leads to verify, and every reported technology must cite **public web evidence** — a job posting, engineering blog, documentation, a case study, or a live website fingerprint from the company's own site.
A vendor row is never itself the citation. A hint that can't be corroborated on the public web surfaces as `status: not_found` — an explicit "checked but unconfirmed" element — rather than being reported as a confirmed finding or silently dropped. This is what keeps the signal from regurgitating a technographics column: the answer reflects what could be verified, not what a data provider guessed.
## Request [#request]
Supply a `domain` plus **exactly one** of `category` or `technology`. Providing neither, or both, returns a `422`.
```bash
# Category scope: which CRM does this company use?
curl -X POST https://api.saber.app/v1/companies/enrich/tech-stack \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "monday.com",
"category": "crm"
}'
```
```bash
# Technology scope: does this company use Salesforce?
curl -X POST https://api.saber.app/v1/companies/enrich/tech-stack \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "monday.com",
"technology": "salesforce"
}'
```
| Field | Type | Required | Description |
| -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `domain` | string | yes | Company domain to research. |
| `category` | string | one of | Coarse category to survey: `erp` or `crm`. Mutually exclusive with `technology`. |
| `technology` | string | one of | A specific technology to check for, as free text (e.g. `salesforce`, `SAP S/4HANA`); Saber resolves it to a canonical registry slug. Mutually exclusive with `category`. |
| `webhookUrl` | string | no | Where to deliver the completed result when the run finishes (`202` path). On a `200` cache serve the answer is already in the response. |
| `forceRefresh` | boolean | no | Bypass the cache and force a fresh run. |
An unresolvable `technology` value returns a `422` with did-you-mean suggestions, so a typo fails fast rather than researching the wrong product.
## Response [#response]
Both scopes return the same shape; `scope` tells you which was asked. A completed run carries the payload under `result` (the legacy `/v1/signals` routes return it under `answer.jsonSchema`).
```json
{
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
```
| Field | Type | Description |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scope` | string | `category` or `technology` — which question was asked. |
| `category` | string | The requested category (`erp`/`crm`). Present for category-scope answers. |
| `technologies` | array | Technologies investigated, strongest evidence first. Each carries a canonical `technology` slug, `displayName`, `confidence`, `status`, `lastEvidence`, and an `evidence` list. |
| `primary` | string | Canonical slug of the single current system of record; omitted when no clear primary exists. |
Each technology's `status` is one of:
* `active` — current use, backed by recent public evidence.
* `churned` — evidence shows the company moved off it (e.g. a migration announcement).
* `not_found` — a vendor-data hint was checked but no public evidence of current use was found. Its `evidence` list is empty.
Every `evidence` entry has a `type` (`job_posting`, `engineering_blog`, `docs`, `case_study`, or `website_fingerprint`) and a public `url`. A `website_fingerprint` is a technology observed live on the company's own site (a script, cookie, header, or DOM detection) — direct public evidence, distinct from any vendor data row.
# Getting Started (/getting-started)
Start here if you are new to Saber. The fastest path is to create an API key, run one prebuilt enrichment, then choose the interface that matches your workflow.
# Installation (/getting-started/installation)
Connect Saber to the tools you already use to start uncovering enrichments and intel on any company or contact.
## REST API [#rest-api]
Use the Saber REST API directly to run enrichments and retrieve intelligence on any company from your own code or tools.
Generate a Saber API key from your dashboard: [Go to API Keys](https://ai.saber.app/api-keys)
Run a [prebuilt enrichment](/enrichments) — pass a domain and get a structured answer back, with no question to write:
```bash
curl -X POST https://api.saber.app/v1/companies/enrich/funding \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "stripe.com" }'
```
Or write a custom company enrichment when you need a question the library doesn't cover:
```bash
curl -X POST https://api.saber.app/v1/companies/enrich/custom \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "stripe.com",
"question": "What is the company hiring for right now?",
"answerType": "open_text"
}'
```
A fresh run returns `202` with a `Location: /v1/enrichment-runs/{runId}` header — poll that, or pass a `webhookUrl` to have the completed run delivered. A recent identical request returns the completed run inline as `200`.
> See the [Enrichments guide](/enrichments) for the run envelope and the [API Reference](/api) for the full schema.
## Claude Code [#claude-code]
Install the Saber MCP server into Claude Code so it can look up company enrichments and intelligence directly inside your coding sessions.
Run the install command in your terminal to add the Saber MCP server to Claude Code.
```bash
claude mcp add --transport http saber https://mcp.saber.app/mcp
```
Restart Claude Code. Saber will appear as an available MCP tool.
Ask Claude Code about any company and it will query Saber automatically.
> Try: *"What is stripe.com hiring for, according to Saber?"* or *"Summarise recent activity for acme.com using Saber"*
## HubSpot [#hubspot]
Sync Saber company enrichments directly into HubSpot company properties so your sales team always has the latest intelligence in their CRM.
Install the Saber app from the HubSpot App Marketplace: [View on HubSpot Marketplace](https://ecosystem.hubspot.com/marketplace/apps/saber)
Authorise the connection using your Saber account credentials.
Map Saber enrichment fields to your HubSpot company properties in the settings panel.
Choose a sync frequency (real-time or daily) and save.
Saber now populates custom properties on every HubSpot company record.
> Use enrichment properties in HubSpot views, sequences, and reports to prioritise outreach.
## Clay [#clay]
Enrich your Clay tables with Saber company enrichments using a custom HTTP enrichment.
Create a Saber API key from your dashboard: [Go to API Keys](https://ai.saber.app/api-keys)
Start from the Saber template in Clay: [Open Clay Template](https://app.clay.com/shared-table/share_0tb0prqBxvCtiRg6MYS)
Add your Saber API key as a Credential in Clay. Open the enrichment in the template, click the key icon next to the Authorization header, and save it as **"Saber API Key"**.
Map the domain column from your table to the enrichment input.
Map the response fields to new columns in your table.
```
result.hiring → hiring_activity
result.technographics → tech_stack
```
Run the enrichment on your company list.
> Use Clay formulas to score or segment companies based on the enriched data.
## n8n [#n8n]
Trigger Saber enrichment lookups inside n8n workflows to automatically enrich leads, update CRM records, or fire alerts when companies show buying intent.
Create a Saber API key from your dashboard: [Go to API Keys](https://ai.saber.app/api-keys)
In n8n, add an **HTTP Request** node to your workflow.
Configure the node to call the Saber enrichment endpoint.
```json
{
"method": "POST",
"url": "https://api.saber.app/v1/companies/enrich/custom",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{ "name": "domain", "value": "={{ $json.domain }}" },
{ "name": "question", "value": "What is the company hiring for?" },
{ "name": "answerType", "value": "open_text" }
]
}
}
```
Add an Authorization header with your Saber API key as a Bearer token.
Connect the output to downstream nodes.
> Use an IF node to branch based on enrichment values. For example, route high-intent companies to a priority Slack channel.
## Make [#make]
Add Saber to Make scenarios with an HTTP module to enrich data or trigger alerts based on company enrichments.
Create a Saber API key from your dashboard: [Go to API Keys](https://ai.saber.app/api-keys)
In your Make scenario, add an **HTTP > Make a request** module.
Configure the module to call the Saber enrichment endpoint.
```
URL: https://api.saber.app/v1/companies/enrich/custom
Method: POST
Headers:
Authorization: Bearer YOUR_SABER_API_KEY
Content-Type: application/json
Body (JSON):
{
"domain": "{{1.domain}}",
"question": "What is the company hiring for?",
"answerType": "open_text"
}
```
Map the JSON response fields to variables using a **JSON Parse** module.
Connect to downstream modules.
> Log enriched data to Google Sheets on a schedule, or attach to a webhook trigger to enrich inbound form submissions in real time.
# Introduction (/getting-started/introduction)
Saber is a sales intelligence platform for turning company and contact research into structured, cited answers. Teams use Saber to enrich CRM records, qualify inbound leads, prioritize accounts, prepare for meetings, and automate GTM research workflows.
## Quick Start [#quick-start]
Choose the entry point that matches how your team works.
## What is an Enrichment? [#what-is-an-enrichment]
An **enrichment** answers one specific research question about a company or contact and returns a structured, cited result. A good question is observable, structured, and useful downstream. For example, "Is this company hiring account executives?" is easier to route and score than "Is this a good account?"
Each enrichment **run**:
1. Researches the target using available public and connected sources.
2. Analyzes the evidence against your question.
3. Returns the answer in the requested shape.
4. Includes confidence and source context where available.
Enrichments support multiple answer types: `open_text`, `number`, `boolean`, `list`, `percentage`, `currency`, `url`, and `json_schema`.
**"Signal" means something specific in Saber.** A *signal* is an occurrence emitted by a [monitor](/monitors) — a funding round detected, an answer that changed — **not** a research answer. Research answers are **enrichments**. (The question-answering endpoints once called the "Signals API" are now the [Enrichments API](/enrichments); the legacy `/v1/…/signals` routes keep working until their announced sunset.)
## Core Concepts [#core-concepts]
**Companies and contacts**
Research companies by domain and contacts by profile or enriched contact data. Company enrichments are best for account qualification, timing, and market context. Contact enrichments are best for persona research, outreach personalization, and meeting preparation.
**Lists**
Use lists when a target segment or audience should survive beyond a one-off search. Lists give you a stable boundary for review, enrichment, scoring, CRM sync, and recurring [monitor](/monitors) runs.
**Templates**
[Enrichment templates](/api/enrichment-templates) standardize reusable questions and answer types. They are useful when multiple people, agents, or jobs need to score accounts using the same evidence, and a [watch monitor](/monitors) re-runs one on a schedule.
**Monitors**
A [monitor](/monitors) is a standing watch that emits **signals** — occurrences such as a changed answer or a detected funding round — over time. Use monitors when you care about *what changed*, not a one-off answer.
**Connectors**
Connectors link Saber to external systems such as HubSpot and LinkedIn Sales Navigator. Check connector status before workflows that depend on CRM or contact source data.
## How it works [#how-it-works]
The fastest first call is a [prebuilt enrichment](/enrichments) — pass a domain and get a structured, evidence-backed answer, with no question to design:
```ts
const res = await fetch('https://api.saber.app/v1/companies/enrich/funding', {
method: 'POST',
headers: {
Authorization: 'Bearer ',
'Content-Type': 'application/json',
},
body: JSON.stringify({ domain: 'openai.com' }),
});
// A fresh run returns `202` with `Location: /v1/enrichment-runs/{runId}` — poll
// that run or pass a `webhookUrl` for delivery. A cache serve returns `200`
// inline (free). Both carry the `enrichment_run` envelope.
const run = await res.json();
console.log(run.result);
```
When your question is specific to your niche — something no prebuilt type covers — write a [custom enrichment](/enrichments/custom) with your own `question` and `answerType`:
```ts
const res = await fetch('https://api.saber.app/v1/companies/enrich/custom', {
method: 'POST',
headers: {
Authorization: 'Bearer ',
'Content-Type': 'application/json',
},
body: JSON.stringify({
domain: 'openai.com',
question: 'What AI products does this company offer?',
answerType: 'open_text',
}),
});
const run = await res.json();
console.log(run.result);
```
Both routes return the `enrichment_run` envelope: `202` starts a fresh run (poll `GET /v1/enrichment-runs/{runId}` or receive the result via `webhookUrl`), and `200` serves a cached answer inline. `GET /v1/enrichment-runs` lists runs across both entity types.
## AI Tooling [#ai-tooling]
Saber is designed for both programmatic integrations and AI-native workflows:
1. Use the [CLI](/cli) for repeatable terminal, CI, and agent-session workflows.
2. Use the [MCP Server](/mcp) when an AI assistant should call Saber tools directly.
3. Use [Skills](/skills) when an agent should follow a complete GTM workflow such as market mapping, list building, scoring, or outreach.
## Next Steps [#next-steps]
# Saber Docs (/)
Saber helps teams turn public company and contact data into structured research signals. Use these docs to build with the REST API, run workflows from the CLI, connect Saber to AI agents through MCP, and install Saber Skills for GTM workflows.
# Account Management (/mcp/capabilities/account-management)
## Integrations [#integrations]
### List connectors [#list-connectors]
Check which integrations are active on your account — LinkedIn Sales Navigator, HubSpot, and others — and whether any need re-authentication. Call this if a tool returns an unexpected error related to a missing integration.
## Credits [#credits]
### Get credits [#get-credits]
Check your remaining API credit balance for the current billing period. Call this before large batch jobs to confirm you have sufficient credits.
# Company Enrichments (/mcp/capabilities/company-enrichments)
Enrichments are AI-generated answers to research questions about a company. Ask anything — tech stack, funding status, headcount, recent news — and Saber returns a sourced answer as an enrichment run.
## Creating enrichments [#creating-enrichments]
### Create enrichment [#create-enrichment]
Creates an enrichment run and returns immediately with a pending status. Use [Get run](#get-run) to poll for the result, or provide a `webhookUrl` to receive it automatically when ready.
### Create enrichment batch [#create-enrichment-batch]
Run the same question across many companies at once — each company becomes its own run, billed and cached per company. Efficient for list enrichment workflows.
## Retrieving runs [#retrieving-runs]
### List runs [#list-runs]
Browse past enrichment runs. Filter by entity, type, date range, or status. Useful for checking the output of a batch job or reviewing historical research.
### Get run [#get-run]
Retrieve a specific enrichment run by ID. Use this to poll for the result of a run created asynchronously.
## Summaries [#summaries]
### Generate summary [#generate-summary]
After running several enrichments for a domain, call this to have Saber's AI consolidate the individual answers into a single coherent written brief — company overview, tech stack, hiring activity, recent activity.
### List summaries [#list-summaries]
Retrieve previously generated summaries for a domain. Useful for retrieving a brief without regenerating it.
# Contact Intelligence (/mcp/capabilities/contact-intelligence)
Contact intelligence tools let you research individuals before meetings, ask free-form questions about a person, and discover contacts at a company via LinkedIn Sales Navigator.
## Contact research [#contact-research]
### Create contact research [#create-contact-research]
Start an async job that gathers AI insights about a person from LinkedIn and other sources. Returns a job ID immediately; use [Get contact research](#get-contact-research) to poll for results.
Results include structured insights on background, career history, current role, and areas of expertise.
### Get contact research [#get-contact-research]
Retrieve contact research by job ID. Returns the job status and, when complete, the full structured insights.
### Get contact research by external ID [#get-contact-research-by-external-id]
Look up contact research using an external system ID (e.g. a HubSpot contact ID) and source name. Useful when you have the CRM identifier but not the Saber job ID.
## Contact enrichments [#contact-enrichments]
### Create contact enrichment [#create-contact-enrichment]
Ask a free-form question about a specific contact and get the answer asynchronously. Works like company enrichments but targets a person rather than a company.
### List contact enrichments [#list-contact-enrichments]
Browse past contact enrichments, filtered by contact ID or date.
### Get contact enrichment [#get-contact-enrichment]
Retrieve a specific contact enrichment by ID.
## Contact search [#contact-search]
### Search contacts [#search-contacts]
Find people at a company via LinkedIn Sales Navigator. Search by job title, name, or keywords. Returns profiles with name, title, and LinkedIn URL.
Requires a LinkedIn Sales Navigator connection on your Saber account. Check **Settings → Connectors** in the Saber app if contact search returns no results.
# Enrichment Templates (/mcp/capabilities/enrichment-templates)
Enrichment templates let you define a named set of questions once and reuse them across many enrichments and batch jobs. Templates are especially useful for enforcing consistent research standards across a team.
## Managing templates [#managing-templates]
### Create template [#create-template]
Define a new template with a name and a fixed set of questions. Once created, the template can be referenced by ID when creating enrichments or batch jobs.
### List templates [#list-templates]
Browse all enrichment templates on your account. Returns template names, IDs, and question counts.
### Get template [#get-template]
Retrieve a specific template by ID, including its full question list and version history.
### Update template [#update-template]
Update a template's name or question set. Updates create a new version and preserve previous versions — existing runs created from the template are not affected.
### Delete template [#delete-template]
Soft-delete a template. The template is no longer available for new enrichments but existing run history is preserved.
# Capabilities (/mcp/capabilities)
The Saber MCP Server exposes named tools for the core Saber workflows available through the public API.
## Company enrichments [#company-enrichments]
AI-generated answers to free-form research questions about a company. Includes async enrichment creation, batch enrichment, summaries, and reusable templates.
→ [Company Enrichments](/mcp/capabilities/company-enrichments)
## Signal templates [#signal-templates]
Reusable question sets for standardised research across your team. Define a template once and reference it by name in batch jobs.
→ [Enrichment Templates](/mcp/capabilities/enrichment-templates)
## Contact intelligence [#contact-intelligence]
Research a person before a meeting, ask questions about a contact, or find people at a company via LinkedIn Sales Navigator.
→ [Contact Intelligence](/mcp/capabilities/contact-intelligence)
## Account management [#account-management]
Check which integrations are active on your account and monitor your API credit balance.
→ [Account Management](/mcp/capabilities/account-management)
# Basic Usage (/mcp/getting-started/basic-usage)
Once connected, your AI assistant can call Saber tools directly. You do not need to think about which tool to use. Describe what you want to know and the assistant picks the right tool automatically.
## Research a company [#research-a-company]
Ask your assistant a question about a company by domain:
> *"What is Acme Corp's tech stack?"*
> *"Is linear.app venture-backed or bootstrapped?"*
> *"How large is Stripe's engineering team?"*
The assistant calls `createCompanySignal`, waits for the result, and returns the answer. Each question is independent, so you can run several in parallel:
> *"For notion.so: what's their tech stack, headcount, and funding status?"*
## Qualify a prospect [#qualify-a-prospect]
Use the built-in `qualify-account` prompt to score a company against your ICP criteria:
> *"Qualify acme.com. We target Series A or later, 50-500 employees, US-headquartered, using Salesforce."*
The assistant gathers the relevant enrichments and returns a verdict with evidence for each criterion.
## Research before a sales call [#research-before-a-sales-call]
Use the built-in `research-account` prompt for a structured pre-call brief:
> *"Research acme.com before my call tomorrow."*
The assistant runs several enrichments in parallel, then calls `companies-generate_summary` to produce a brief with company overview, tech stack, hiring activity, recent news, and recommended talking points.
## Find contacts at a company [#find-contacts-at-a-company]
> *"Find the VP of Engineering at stripe.com."*
> *"Who are the decision makers at acme.com?"*
The assistant calls `searchContacts` using LinkedIn Sales Navigator. Requires a Sales Navigator connection on your Saber account. Check **Settings > Connectors** in the Saber app.
## Research a contact [#research-a-contact]
> *"Research the contact at linkedin.com/in/janedoe before my meeting."*
The assistant calls `createContactResearch`, polls until complete, and returns a contact brief with background, expertise, likely pain points, and personalised talking points.
## Check your credit balance [#check-your-credit-balance]
> *"How many Saber credits do I have left?"*
The assistant calls `getCredits` and returns your remaining balance for the current billing period.
## Tips [#tips]
* **Run enrichments in parallel.** Multiple questions about the same company are independent. Ask them all at once and the assistant will call the tools concurrently.
* **Use `companies-generate_summary` after batch research.** If you've run several enrichments for a domain, this consolidates them into a coherent written brief.
* **Check `listConnectors`** if contact search isn't working. Your LinkedIn Sales Navigator connection may need refreshing.
# Installation (/mcp/getting-started/installation)
The Saber MCP Server is hosted at `https://mcp.saber.app/mcp`. No installation or self-hosting is required — just point your client at that URL and authorize with your Saber account.
## Claude Code [#claude-code]
The fastest way to connect. Run this command once:
```bash
claude mcp add --transport http saber https://mcp.saber.app/mcp
```
On first use, Claude Code will open the OAuth flow in your browser. Enter your Saber API key when prompted. After that, the connection is stored and reused automatically.
To verify it was added:
```bash
claude mcp list
```
To remove it:
```bash
claude mcp remove saber
```
## Claude Desktop [#claude-desktop]
Add the following to your Claude Desktop config file:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"saber": {
"type": "streamable-http",
"url": "https://mcp.saber.app/mcp"
}
}
}
```
Restart Claude Desktop after saving the file. On first use, you'll be redirected to authorize with your Saber account.
## Cursor [#cursor]
Open Cursor's MCP settings and add a new server:
```json
{
"mcpServers": {
"saber": {
"type": "streamable-http",
"url": "https://mcp.saber.app/mcp"
}
}
}
```
## Other clients [#other-clients]
Any MCP client that supports Streamable HTTP transport can connect. Point it at:
```
https://mcp.saber.app/mcp
```
OAuth metadata is served at `https://mcp.saber.app/.well-known/oauth-authorization-server` and is discovered automatically by compliant clients.
## Getting a Saber API key [#getting-a-saber-api-key]
During the OAuth flow, you'll be asked to enter a Saber API key. To get one:
1. Log in to [ai.saber.app](https://ai.saber.app)
2. Navigate to **Settings** → **API Keys**
3. Click **Create API Key**, give it a name, and copy it immediately — it's only shown once
# MCP Server (/mcp)
The Saber MCP Server brings Saber's full sales intelligence API into your AI workflow. Connect any MCP-compatible client — Claude Code, Claude Desktop, Cursor, or your own agent — and research companies, qualify prospects, and gather contact insights without leaving your editor.
## What is MCP? [#what-is-mcp]
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) is an open standard for connecting AI assistants to external tools and data sources. The Saber MCP Server implements this protocol, exposing the Saber API as a set of AI-callable tools your assistant can invoke on demand.
## How it works [#how-it-works]
1. **Connect your client**: point any MCP-compatible AI client at `https://mcp.saber.app/mcp`.
2. **Authorize via OAuth**: the client opens a browser flow where you enter your Saber API key.
3. **Start researching**: ask your AI to look up a company, find contacts, or run a batch of enrichments. The right tools are called automatically.
The MCP server exposes Saber capabilities as named tools, so assistants can run the same product workflows available through the public API.
## Next steps [#next-steps]
* [Installation](/mcp/getting-started/installation): connect your client in under a minute
* [Basic Usage](/mcp/getting-started/basic-usage): practical examples to get started
* [Capabilities](/mcp/capabilities): full reference of all available tools
# Prompts (/mcp/prompts)
Prompts are reusable message templates your AI client can invoke by name. They set up the context and tool sequence for common Saber workflows, so you don't have to describe the steps each time.
Invoke a prompt by name in your AI client — for example, in Claude Code: `/mcp__saber__research-account`.
## Research account [#research-account]
Research a company before a sales call. Runs key enrichment questions in parallel, then generates a consolidated brief.
**Arguments:**
| Argument | Required | Description |
| -------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `domain` | Yes | The company's website domain (e.g. `acme.com`) — no `https://` or `www.` |
| `focus` | No | Optional focus area (e.g. `"tech stack"`, `"hiring trends"`, `"funding status"`). Leave blank for a general overview. |
**What it does:**
1. Runs `createCompanySignal` in parallel for: company overview, revenue, funding, engineering team size, tech stack, hiring trends, and recent news
2. Calls `companies-generate_summary` to consolidate the enrichments
3. Returns a structured sales brief with: Company Overview, Technology, Growth & Hiring Signals, Recent Activity, Buying Signals, and Recommended Talking Points
## Qualify account [#qualify-account]
Qualify a company against your ICP criteria and return a scored verdict with evidence.
**Arguments:**
| Argument | Required | Description |
| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `domain` | Yes | The company's website domain (e.g. `acme.com`) |
| `criteria` | No | Your qualification criteria as a description or list (e.g. `"Series A or later, 50–500 employees, uses Salesforce, US-headquartered"`). If not provided, the assistant will ask before proceeding. |
**What it does:**
1. For each criterion, runs `createCompanySignal` with a focused question to gather evidence
2. Returns a **Verdict** (Qualified / Needs Review / Disqualified), a criterion-by-criterion breakdown table, and a 2–3 sentence summary
## Research contact [#research-contact]
Research a contact before a meeting. Gathers LinkedIn insights and surfaces personalised conversation openers.
**Arguments:**
| Argument | Required | Description |
| ---------------- | -------- | ----------------------------------------------------------------------------------------- |
| `linkedin_url` | Yes | The contact's LinkedIn profile URL (e.g. `https://linkedin.com/in/janedoe`) |
| `company_domain` | No | The contact's company domain (e.g. `acme.com`). Enables additional company-level context. |
**What it does:**
1. Calls `createContactResearch` with the LinkedIn URL and polls until complete
2. Optionally runs `createCompanySignal` to understand the company context in parallel
3. Returns a contact brief with: Background, Expertise & Interests, Likely Pain Points, Conversation Openers, and Things to Avoid
# Resources (/mcp/resources)
MCP resources are read-only data that AI clients can browse and read, similar to files in a filesystem. The Saber MCP Server exposes one resource.
## `saber://capabilities` [#sabercapabilities]
A Markdown guide to all Saber MCP tools, what they do, when to use each one, and tips for best results.
**URI:** `saber://capabilities`
**MIME type:** `text/markdown`
This resource is intended to be read by the AI assistant at the start of a session to orient itself on what Saber can do. Most MCP clients load resources automatically; you can also ask your assistant to read it explicitly:
> *"Read the Saber capabilities guide."*
The content mirrors the [Capabilities](/mcp/capabilities) page in this documentation.
# Monitors (/monitors)
A **monitor** is a standing watch that emits **signals** over time. A *signal* here is an occurrence — something that happened — not a research answer (those are [enrichments](/enrichments)).
Two kinds, one resource:
* **Watch monitors** re-check the entities on one of your lists with an [enrichment template](/api/enrichment-templates) and emit a `result_changed` signal when an answer changes. Silent no-change runs emit nothing.
* **Discovery monitors** watch the market for occurrences of a type — `funding_raised`, `job_posted`, `social_posted`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published` — matching your filters or prompt.
## Public tenders [#public-tenders]
The `tender_published` monitor finds public procurement notices that you can still bid on. It reads TED (EU), SAM.gov (US federal) and UK Contracts Finder.
**Availability:** this monitor is not available in production yet. Until Saber enables it, a request to create a `tender_published` monitor returns `422`.
**Scope.** The monitor finds only software, AI and automation work. It narrows the notices two times:
1. It reads only these procurement codes: CPV divisions `72`, `48`, `30` and `73` from TED and UK Contracts Finder, and NAICS codes `541511`, `541512`, `541519` and `513210` from SAM.gov.
2. A classifier examines each notice. The monitor delivers a notice only if the classifier finds software, AI or automation work in it.
Your `cpvCodes` and `naicsCodes` filters narrow this scope. They cannot make it wider. For example, a monitor with `cpvCodes: ["45"]` (construction work) and `sources: ["ted", "uk_contracts_finder"]` gets only the notices that have an in-scope code and also a construction code. A notice can have more than one CPV code, and one matching code is sufficient. Without `sources`, the same monitor also gets the SAM.gov notices in scope, because `cpvCodes` does not apply to SAM.gov.
Each list applies only to the portals that use that code system. `cpvCodes` narrows TED and UK Contracts Finder. `naicsCodes` narrows SAM.gov. A list does not remove the notices of the other portals: a monitor with `naicsCodes` only still gets EU and UK notices. To read fewer portals, set `sources`.
The monitor emits only open opportunities. It does not emit an award notice, because a supplier has already won that contract. It does not emit a notice after its submission deadline.
**Filter by procurement code, not by keyword.** A notice description is published in the language of the buyer, and it is not translated. A keyword filter therefore finds the English notices and misses the others. Procurement codes have no language.
| Filter | Effect |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `sources` | One or more of `ted`, `sam_gov`, `uk_contracts_finder`. |
| `cpvCodes` | EU and UK codes, matched as a prefix. `72` selects all IT services. Applied only to TED and UK Contracts Finder notices. |
| `naicsCodes` | US codes, matched as a prefix. Applied only to SAM.gov notices. |
| `buyerCountries` | ISO 3166-1 alpha-2 country codes. |
| `minValueEur` | A minimum contract value in EUR. The monitor compares only values that a notice states in EUR. |
| `deadlineWithinDays` | Only notices that close in this number of days. |
| `categories` | One or more of `software`, `ai`, `automation`. |
| `promptFilter` | Your own description of the work you want. |
A filter you do not set does not narrow the results.
The monitor reads the last **two days** of notices. This window is fixed. A tender is a live opportunity with a deadline, and an older notice is one your team has already seen or can no longer bid on.
Two rules keep opportunities in your feed:
* A notice that states no contract value, or states it in a different currency, passes `minValueEur`. Many buyers publish no budget, and Saber does not convert currencies.
* A notice that states no deadline passes `deadlineWithinDays`.
Each signal payload contains an English summary, so you can read a notice in any language.
## Working with monitors [#working-with-monitors]
Create one with `POST /v1/monitors` (`checks: [{kind, …}]` — exactly one check today), manage it with `PATCH`/`DELETE` and `pause`/`resume`/`trigger`, and read what it found on `GET /v1/monitors/{monitorId}/signals`: the occurrence envelope carries `type`, `occurredAt`, `observedAt`, `entity`, and a type-specific `payload`. Deliver occurrences as they happen with a `webhook`.
## Pricing [#pricing]
Configuration, lifecycle, and reads are free. Each signal a monitor **emits** is charged under the `monitor-signals` feature (see [/pricing](/pricing) for the current rate) — a watch run that detects no change emits nothing and costs nothing.
## Migrating from subscriptions [#migrating-from-subscriptions]
Monitors replace two legacy families — signal subscriptions (`/v1/companies/signals/subscriptions/*`, `/v1/contacts/signals/subscriptions/*`) and market-signal subscriptions (`/v1/market-signals/*`). Both keep working **at their old prices** until their announced sunset; their responses carry `Deprecation` and successor `Link` headers.
| Legacy | Successor |
| --------------------------------------- | ---------------------------------------------------------- |
| Create/list/get/update a subscription | `/v1/monitors` (`?kind=watch\|discovery` filters the list) |
| `start` / `stop` | `resume` / `pause` |
| `trigger` | `trigger` |
| Subscription `/logs`, market `/signals` | `GET /v1/monitors/{monitorId}/signals` |
A subscription created through `/v1/monitors` bills per emitted signal; a pre-existing legacy subscription keeps its legacy pricing until you delete and re-create it on the new surface.
# Signals (/monitors/signals)
A **signal** is one real-world occurrence a [Monitor](/monitors) detects and emits: a round was raised, a role was posted, an IPO was announced. Each occurrence is emitted once, carrying its field set, and delivered to the monitor's webhook.
> **One meaning.** "Signal" used to be the name for one-off research calls — those are now [Enrichments](/enrichments). Today *signal* means only the occurrence a Monitor emits. When you read an old doc or an old route, that's the mapping.
## Signal types [#signal-types]
A monitor emits one occurrence type per detected event. Types are **verb-past** — they name something that happened. A discovery (`signal`) check emits the types below; an `enrichment` check emits `result_changed` (see [Enriching a signal](#enriching-a-signal)).
| Type | Occurrence |
| ------------------- | ------------------------------------------------------------------------------------- |
| `funding_raised` | The company raised a funding round |
| `investment_made` | The company made an investment |
| `ipo_announced` | The company announced an IPO / listing |
| `ipo_stage_changed` | An in-flight IPO changed stage |
| `job_posted` | The company posted a role |
| `social_posted` | A tracked social post was published |
| `result_changed` | A watched enrichment's answer changed between runs (emitted by an `enrichment` check) |
## Reading emitted signals [#reading-emitted-signals]
Poll `GET /v1/monitors/{monitorId}/signals` for the occurrences a monitor has emitted, or receive each one at your webhook as it happens. Every signal carries its `type`, when it happened (`occurredAt`) and when the monitor observed it (`observedAt`), the `entity` it concerns, and a structured `payload`.
## Enriching a signal [#enriching-a-signal]
A monitor can attach an [enrichment](/enrichments) to each signal it emits — an `enrichment` check runs a saved [template](/api/enrichment-templates) on every detected occurrence, so the extra fields arrive in the same delivery. This is how a "funding raised" signal can also carry, say, the lead investor's profile: the monitor detects the occurrence, then enriches it before delivery.
# Pricing (/pricing)
Saber API usage is metered in **credits**. This page lists the standard credit cost for every metered operation; the same values appear as `x-credit-cost` on each operation in the [API reference](/api). Organizations with a custom agreement may have different rates. Prices last updated: .
## How billing works [#how-billing-works]
* **Charges apply on success only.** A call that fails, is rejected, or can't be processed is never charged; reserved credits are released.
* **Cached results don't charge twice.** Repeat calls that return a result your organization already paid for are free.
* **Async and sync cost the same.** You pay for the answer, not the transport. Batch operations charge per item.
* **Everything not listed below is free** — every read, listing, template, configuration, and lifecycle operation.
Check your balance anytime with `GET /v1/credits` (free).
## Metered operations [#metered-operations]
## Charges not tied to a single call [#charges-not-tied-to-a-single-call]
## Running out of credits [#running-out-of-credits]
Requests that would exceed your remaining balance return `402` with an `INSUFFICIENT_CREDITS` error code and your remaining balance in `error.details.remainingCredits` — before any billable work starts. Credits reset at the start of each billing period.
# Rate Limits (/rate-limits)
Saber applies rate limits to authenticated public API requests to protect shared infrastructure and keep long-running research workloads predictable.
Rate limits are scoped to the API key. Requests authenticated through HubSpot are scoped to the HubSpot hub ID instead.
## Default limits [#default-limits]
| Request type | Default limit | Burst capacity | Notes |
| ---------------------------------------- | --------------------------: | -------------: | ---------------------------------------------------------------------------------------------------- |
| Write requests (`POST`, `PUT`, `DELETE`) | 2,000 requests per minute | 400 | Used for operations that create, update, delete, or trigger work. |
| Read requests (`GET`, `HEAD`, `OPTIONS`) | 100,000 requests per minute | 10,000 | Higher limit for polling, status checks, and read-only list calls. |
| Signal template proposal | 6 requests per minute | 3 | Extra per-route cap for `POST /v1/signal-templates/extract/propose` because it triggers an LLM call. |
These are the default platform limits. Your organization may have different limits depending on plan, environment, or custom configuration.
## Response headers [#response-headers]
Every rate-limited public API response includes headers that describe the current bucket:
| Header | Description |
| ----------------------- | -------------------------------------------------- |
| `X-Ratelimit-Limit` | Maximum requests allowed in the current bucket. |
| `X-Ratelimit-Remaining` | Requests remaining before the bucket is exhausted. |
| `X-Ratelimit-Reset` | Unix timestamp for the next reset window. |
Use these headers to slow down before receiving a `429`.
## 429 responses [#429-responses]
When the global API-key limit is exceeded, Saber returns:
```json
{
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 12
}
```
`retryAfter` is the number of seconds to wait before sending another request.
Some endpoint-specific limiters can return the standard Saber error envelope instead:
```json
{
"error": {
"type": "TIMEOUT",
"code": "TOO_MANY_REQUESTS",
"message": "Per-route rate limit exceeded",
"requestId": "..."
}
}
```
In client code, handle both shapes for HTTP `429`.
## Retry strategy [#retry-strategy]
When you receive a `429`:
1. Wait for `retryAfter` seconds if it is present.
2. Otherwise wait until the `X-Ratelimit-Reset` timestamp.
3. Add jitter before retrying if many workers share the same API key.
4. Keep retries bounded so failed jobs do not loop forever.
For polling workflows, prefer the higher-capacity read endpoints and back off when `X-Ratelimit-Remaining` gets low.
## Credits are separate [#credits-are-separate]
Rate limits control request volume. Credits control billable work.
Creating enrichments and some batch operations can consume credits even if you stay within the request limit. Check your balance before large runs:
```bash
saber credits
```
Or call the credits endpoint directly:
```bash
curl https://api.saber.app/v1/credits \
-H "Authorization: Bearer "
```
# Authoring Skills (/skills/authoring)
Saber Skills are structured agent workflows for repeatable GTM jobs. A strong skill does not just describe a feature. It tells the agent what context to gather, which Saber capabilities to use, what fallback path to follow, and what output the user should receive.
## What a skill should include [#what-a-skill-should-include]
* A clear job boundary, such as account research, list building, outreach, or deal review.
* A first action that gets the workflow moving without unnecessary questions.
* Tool checks for optional capabilities such as the Saber CLI, MCP tools, HubSpot, Apollo, or enrichment providers.
* A useful fallback path when a tool is unavailable.
* A concrete output format such as a table, ranked list, account brief, sequence draft, or CRM field mapping.
## Quality bar [#quality-bar]
Good skills are operationally useful. They make credit usage explicit, preserve source context, and avoid broad research prompts when the workflow needs a structured answer. They should help an agent choose between the CLI, MCP tools, pasted data, or manual research based on what is available in the user's environment.
## Example shape [#example-shape]
```markdown
---
name: my-skill
description: One-line description shown in the agent session header.
---
# My Skill
Start by confirming the target account or segment. Then gather the minimum context needed to complete the workflow, use Saber commands when available, and return a concise decision-ready output.
```
# Catalog (/skills/catalog)
## Signal foundations [#signal-foundations]
| Skill | Use it for |
| ------------------ | -------------------------------------------------------------------------------------------------- |
| `signal-discovery` | Guided entry point that loads org context, extracts ICP, and generates signals. |
| `extract-icp` | Research a company domain and extract target profile, buying committee, pain points, and triggers. |
| `generate-signals` | Turn ICP data into 12 to 15 weighted research signals. |
## Market and persona intelligence [#market-and-persona-intelligence]
| Skill | Use it for |
| ------------------- | ------------------------------------------------------------------------------------- |
| `market-map` | Map segments, density, players, adjacent verticals, and entry points. |
| `persona-research` | Build a buyer persona profile with motivations, pains, buying behavior, and language. |
| `competitive-intel` | Profile competitors and create battle cards. |
## List building and activation [#list-building-and-activation]
| Skill | Use it for |
| ------------------------ | ----------------------------------------------------------------------------- |
| `build-account-list` | Build a target account list from Saber, Apollo, HubSpot, or manual research. |
| `build-contact-list` | Build a target contact list from Saber, Apollo, LinkedIn, or manual research. |
| `enrich-contacts` | Find email and phone data through available enrichment providers. |
| `create-company-signals` | Activate company-level signal tracking. |
| `create-contact-signals` | Activate contact-level signal tracking. |
| `manage-signals` | Review, pause, resume, trigger, and tune active signal subscriptions. |
## Research, scoring, and outreach [#research-scoring-and-outreach]
| Skill | Use it for |
| ------------------- | ------------------------------------------------------------------------------- |
| `research-account` | Produce an account brief with signals, hiring, news, tech stack, and call prep. |
| `qualify-inbound` | Score inbound leads as High, Medium, or Low. |
| `score-accounts` | Rank account lists using weighted signal data. |
| `write-outreach` | Write signal-based email and LinkedIn outreach. |
| `build-sequence` | Design a multi-step sequence with signal-triggered variants. |
| `objection-handler` | Build an objection library for your ICP. |
## Pipeline and CRM [#pipeline-and-crm]
| Skill | Use it for |
| ------------------------- | -------------------------------------------------------------------- |
| `deal-coaching` | Assess deal health, risks, stakeholders, and next steps. |
| `pipeline-review` | Audit pipeline health and forecast risk from CRM or pasted data. |
| `find-expansion-accounts` | Surface upsell and cross-sell opportunities from existing customers. |
| `import-from-hubspot` | Pull HubSpot lists or views into Saber workflows. |
| `sync-to-hubspot` | Push Saber signal results back to HubSpot properties. |
# Skills and CLI (/skills/cli)
Skills are designed to degrade gracefully. Without the CLI, they guide the agent through manual research, pasted exports, or connected MCP tools. With the CLI, they can run Saber-native operations directly.
## CLI unlocks [#cli-unlocks]
| Capability | CLI command |
| ------------------------- | ------------------------------------------ |
| Run company research | `saber signal --domain ... --question ...` |
| Create reusable templates | `saber template create ...` |
| Build account lists | `saber list company create ...` |
| Build contact lists | `saber list contact create ...` |
| Score accounts | `saber scoring ...` |
| Inspect credits | `saber credits` |
| Inspect connectors | `saber connectors` |
## Skill invocation quick reference [#skill-invocation-quick-reference]
Use `$skill-name` in agents that support skills.
| Workflow | Skill | Common CLI handoff |
| -------------------- | ---------------------------------------------------- | ------------------------------------------------------------------- |
| Start a GTM strategy | `$signal-discovery` | Pull organisation context, define templates, then activate signals. |
| Build a list | `$build-account-list`, `$build-contact-list` | Create or import lists, preview counts, and prepare signal runs. |
| Run signals | `$create-company-signals`, `$create-contact-signals` | Check credits, create signal runs, and review results. |
| Prioritise accounts | `$score-accounts` | Apply scoring profiles and inspect detailed score output. |
| Update CRM | `$import-from-hubspot`, `$sync-to-hubspot` | Move HubSpot lists into Saber, then sync signal results back. |
## Recommended setup [#recommended-setup]
```bash
saber auth login
saber init-claude
```
`init-claude` writes organisation context and command guidance into `CLAUDE.md`, which helps skills tailor signal definitions and avoid wasting credits.
# Saber Skills (/skills)
Saber Skills is a GTM skills library for Codex, Claude Code, Cursor, Gemini CLI, and any agent that supports skills. It gives AI agents structured workflows for the full outbound motion: understand the market, activate signals, engage prospects, and manage pipeline.
```text
Understand -> Activate -> Engage
```
Most skills work without special tools. When the Saber CLI is installed and authenticated, skills can run signal research, build lists, score accounts, and sync results more directly.
## Start here [#start-here]
## The workflow model [#the-workflow-model]
| Stage | Skills |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Understand | `signal-discovery`, `extract-icp`, `generate-signals`, `market-map`, `persona-research`, `competitive-intel` |
| Activate | `build-account-list`, `build-contact-list`, `create-company-signals`, `create-contact-signals`, `manage-signals`, `score-accounts` |
| Engage | `research-account`, `qualify-inbound`, `write-outreach`, `build-sequence`, `deal-coaching`, `pipeline-review`, CRM sync skills |
# Installation (/skills/installation)
Saber Skills work best when the Saber CLI is installed and authenticated. The CLI gives agents direct access to Saber commands for signals, lists, scoring, credits, connectors, and organization context. The hosted MCP server is useful when you want Saber tools available through an MCP-compatible client.
## Claude Code [#claude-code]
### Method 1: CLI setup [#method-1-cli-setup]
Install and authenticate the Saber CLI:
```bash
curl -sSL https://raw.githubusercontent.com/saberapp/cli/main/scripts/install.sh | sh
saber auth login
```
Then initialize Claude Code:
```bash
saber init-claude
```
`init-claude` writes Saber context and command guidance into `CLAUDE.md`, so Claude Code can use Saber Skills with your organization profile, connector status, and CLI command guardrails.
### Method 2: MCP setup [#method-2-mcp-setup]
Connect Claude Code to the hosted Saber MCP Server:
```bash
claude mcp add --transport http saber https://mcp.saber.app/mcp
```
On first use, Claude Code opens the OAuth flow in your browser. Enter your Saber API key when prompted. After that, the connection is stored and reused automatically.
Verify the server:
```bash
claude mcp list
```
See [MCP installation](/mcp/getting-started/installation) for other clients and API key setup.
## Other agents via skills.sh [#other-agents-via-skillssh]
```bash
npx skills add saberapp/skills
```
This works with Codex, Claude Code, Cursor, Gemini CLI, GitHub Copilot, and other agents that support skills.
## Optional CLI setup for other agents [#optional-cli-setup-for-other-agents]
Install the Saber CLI to unlock automated signal runs, list management, scoring, and CRM-oriented workflows:
```bash
curl -sSL https://raw.githubusercontent.com/saberapp/cli/main/scripts/install.sh | sh
saber auth login
```
# build-account-list (/skills/reference/build-account-list)
**Stage:** List building
# Build Account List [#build-account-list]
Use this skill to build a list of target accounts and optionally run signals against them.
The Saber CLI is the preferred path, but the skill works with Apollo, HubSpot, or manual
research if you don't have it.
## Step 1 — Confirm criteria [#step-1--confirm-criteria]
Summarise the account criteria from conversation context (ICP, `extract-icp` output, or ask):
* Industry / vertical
* Company size (employee range)
* Geography
* Any additional filters (tech stack, business model, funding stage)
Ask the user to confirm or adjust before proceeding.
## Step 2 — Choose your tool [#step-2--choose-your-tool]
Check what's available and take the best path:
***
### Path A — Saber CLI (recommended) [#path-a--saber-cli-recommended]
Run `saber --help` to confirm the CLI is installed.
**Option 1 — Filter-based list (Saber finds matching companies)**
```bash
saber list company create --name "" --industry "" --country "" --size ""
```
Filter formats:
* `--industry`: lowercase, e.g. `restaurants`, `food & beverages`, `hotels and motels`
* `--size`: `1-10`, `11-50`, `51-200`, `201-500`, `501-1K`, `1K-5K`, `5K-10K`, `10K+`
* `--country`: ISO 3166-1 alpha-2, e.g. `GB`, `DE`, `NL`
* `--technology`: technology slugs, e.g. `hubspot`, `salesforce`, `stripe`
**Important:** if using `--technology`, always run `count-preview` first — it shows how many companies match and the credit cost before charging anything:
```bash
saber list company count-preview --technology "" [--industry] [--country] [--size]
```
Show the result and ask the user to confirm before creating.
**Option 2 — Import from HubSpot**
```bash
saber list company import --name "" --property --operator EQ --value ""
```
**Option 3 — Empty list (user adds companies via dashboard)**
```bash
saber list company create --name ""
```
***
### Path B — Apollo (if Apollo MCP or CLI is available) [#path-b--apollo-if-apollo-mcp-or-cli-is-available]
Use Apollo to search for matching companies with equivalent filters:
* Industry, employee count, geography, technology
* Export the results as a CSV or pull via MCP
If Saber CLI is also available, import the Apollo results into Saber:
```bash
saber list company import --name "" --property domain --operator EQ --value ""
# repeat per domain, or ask the user to bulk-add via the dashboard
```
***
### Path C — HubSpot (if HubSpot MCP is available, no Apollo) [#path-c--hubspot-if-hubspot-mcp-is-available-no-apollo]
Pull companies from HubSpot matching the ICP criteria using the HubSpot MCP:
```
Search HubSpot companies where industry = [X], numberofemployees between [Y] and [Z]
```
Export or work with the results directly. If Saber CLI is available, import into Saber for signal activation.
***
### Path D — Manual / no tools [#path-d--manual--no-tools]
If no prospecting tools are available:
1. Help the user define their filters precisely (industry, size, geo, tech stack)
2. Guide them to search LinkedIn Sales Navigator, Crunchbase, or industry directories
3. Capture results as a structured list:
```
| Company | Domain | Industry | Employees | Country |
|---------|--------|----------|-----------|---------|
| Acme | acme.com | SaaS | 150 | NL |
| ... | | | | |
```
When ready, suggest importing this list into Saber or another signal tool.
***
## Step 3 — Review and confirm [#step-3--review-and-confirm]
Show the list summary (name, company count) and ask the user to confirm before activating signals.
## Step 4 — Sample run (optional but recommended, Saber CLI only) [#step-4--sample-run-optional-but-recommended-saber-cli-only]
Before running signals across the full list, test a sample of the first 10 companies to validate signal quality without spending credits on the full list:
```bash
saber list company companies --limit 10
saber signal --domain --question "" --answer-type boolean --no-wait
saber signal get
```
Present the 10 results and ask: do the signals look accurate? Any false positives? Proceed?
## Step 5 — Run signals on the full list (optional) [#step-5--run-signals-on-the-full-list-optional]
If approved signals are available in conversation context, offer to run them using `create-company-signals`.
## Key Saber commands [#key-saber-commands]
```bash
saber list company count-preview [--technology] [--industry] [--country] [--size]
saber list company create --name "" [--industry] [--country] [--size] [--technology]
saber list company import --name "" --property --operator EQ --value ""
saber list company get
saber list company list
```
# build-contact-list (/skills/reference/build-contact-list)
**Stage:** List building
# Build Contact List [#build-contact-list]
Use this skill to build a list of target contacts and optionally run signals against them.
The Saber CLI is the preferred path, but the skill works with Apollo, LinkedIn, HubSpot,
or manual research if you don't have it.
## Step 1 — Confirm buyer persona [#step-1--confirm-buyer-persona]
Summarise the contact criteria from conversation context (ICP, `persona-research`, or ask):
* Job title(s) and seniority
* Department
* Source account list (if targeting contacts at specific companies)
Ask the user to confirm or adjust before proceeding.
## Step 2 — Choose your tool [#step-2--choose-your-tool]
Check what's available and take the best path:
***
### Path A — Saber CLI (recommended) [#path-a--saber-cli-recommended]
Run `saber --help` to confirm the CLI is installed.
If a target account list exists, get company LinkedIn URLs from it first:
```bash
saber list company companies
```
Then create the contact list:
```bash
saber list contact create --name "" \
--company-linkedin \
--company-linkedin \
--title ""
```
`--company-linkedin` is required. `--title` is repeatable for multiple titles.
***
### Path B — Apollo (if Apollo MCP or CLI is available) [#path-b--apollo-if-apollo-mcp-or-cli-is-available]
Use Apollo to search contacts with matching criteria:
* Title, seniority, department, company filters
* Export results as CSV or pull via MCP
If Saber CLI is also available, the contact list can be imported into Saber via the dashboard for signal activation.
***
### Path C — HubSpot (if HubSpot MCP is available) [#path-c--hubspot-if-hubspot-mcp-is-available]
Pull contacts from HubSpot matching the persona:
```
Search HubSpot contacts where jobtitle contains [title], company.numberofemployees between [Y] and [Z]
```
Work with the results directly. If Saber CLI is available, use LinkedIn URLs from HubSpot to import into Saber.
***
### Path D — LinkedIn search (manual) [#path-d--linkedin-search-manual]
If no tools are available:
1. Guide the user through a Boolean LinkedIn search for the target persona
2. Or describe which accounts and titles to search
3. Capture results in a structured format:
```
| Name | Title | Company | LinkedIn URL | Email (if known) |
|------|-------|---------|--------------|-----------------|
| ... | | | | |
```
Suggest using `enrich-contacts` to add email and phone once the list is built.
***
## Step 3 — Review and confirm [#step-3--review-and-confirm]
Show the list summary and ask the user to confirm before activating signals.
## Step 4 — Sample run (optional but recommended, Saber CLI only) [#step-4--sample-run-optional-but-recommended-saber-cli-only]
Before running signals across the full list, test 3–5 contacts the user already knows to validate signal accuracy without spending credits on the full list.
For each contact:
```bash
saber signal --profile --question "" --answer-type boolean --no-wait
saber signal get
```
Present results and ask: do the signals look accurate? Any false positives? Proceed?
## Step 5 — Run signals on the full list (optional) [#step-5--run-signals-on-the-full-list-optional]
If approved contact signals are available in conversation context, offer to run them using `create-contact-signals`.
## Step 6 — Prioritise [#step-6--prioritise]
After signals complete, present results ranked by intent so the user can sequence outreach.
## Key Saber commands [#key-saber-commands]
```bash
saber list contact create --name "" --company-linkedin [--title] [--keyword] [--country]
saber list contact get
saber list contact list
```
# build-sequence (/skills/reference/build-sequence)
**Stage:** Outreach
# Build Sequence [#build-sequence]
Use this skill to design a full multi-step outreach sequence for a target persona, with each touchpoint personalised using Saber signal data and timing informed by signal triggers.
## Goal [#goal]
Produce a complete sequence plan: N steps, each with a channel, timing, and a personalised message template that references specific signals.
## Step 1 — Establish context [#step-1--establish-context]
Confirm from conversation context or ask:
* **Target persona:** job title, seniority, department
* **Target company profile:** industry, size, signals that matter
* **Sender:** name, company, value prop in one sentence
* **Goal of the sequence:** book a call, get a reply, drive a trial sign-up
If a `signal-discovery` run has already happened in this conversation, use those ICP and signal definitions directly.
## Step 2 — Pull signal context from Saber [#step-2--pull-signal-context-from-saber]
Signal results personalise each step. Pull what's available before designing the sequence.
**If the Saber CLI is available** (`saber --help` works):
Check for existing subscription results covering the target accounts:
```bash
saber subscription list
saber subscription get
```
For contact-level context (if LinkedIn URLs are known):
```bash
saber signal --profile --question "Is this person posting about [relevant topic]?"
```
If no results exist yet, ask the user what signals they typically track and design the sequence around those signal hypotheses — placeholders can be filled in once signals run.
**If the Saber CLI is not available:** design the sequence with signal placeholder hooks (e.g. `[IF hiring signal: ...]`) that the user can personalise once they have data.
## Step 3 — Design the sequence structure [#step-3--design-the-sequence-structure]
Ask the user their preferred sequence length and mix, or recommend based on the persona:
**Recommended structure for cold outreach (B2B SaaS):**
| Step | Day | Channel | Purpose |
| ---- | --- | -------- | -------------------------------------------- |
| 1 | 0 | Email | Signal-led first touch |
| 2 | 3 | LinkedIn | Connection request with context |
| 3 | 7 | Email | Value-add follow-up (resource or insight) |
| 4 | 12 | LinkedIn | Engage on their content (comment or message) |
| 5 | 18 | Email | Direct ask / break-up |
Adjust based on the persona (executives = shorter, fewer steps; SDR targets = more touches).
## Step 4 — Write each step [#step-4--write-each-step]
For each step, write a message template. Use the strongest available signal in Step 1, and distribute different angles across the sequence (don't repeat the same hook).
**Step 1 — Signal-led first touch (email)**
Use the highest-confidence signal as the opener. See `write-outreach` for format guidelines.
**Step 2 — LinkedIn connection request**
Reference the email or a different signal angle. 300 chars max. No hard CTA.
**Step 3 — Value-add follow-up (email)**
Don't push the product again. Share something useful: a relevant case study, a data point tied to their signals, a framework for the problem you solve. Soft CTA.
**Step 4 — LinkedIn engagement**
If the contact has posted recently (use contact-level Saber signals or check LinkedIn), comment meaningfully on their post. If not, send a short message referencing the earlier email.
**Step 5 — Direct ask / break-up (email)**
Be direct. "Is this a priority for you right now?" or "Should I stop reaching out?" — honest, low-friction. Often the highest-response step.
## Step 5 — Add signal-triggered variants [#step-5--add-signal-triggered-variants]
For each step, add a conditional variant based on key signals:
```
Step 1 — IF hiring signal positive:
"I saw [company] is scaling the sales team — most RevOps leaders at that stage are dealing with [problem]..."
Step 1 — IF funding signal positive:
"Congrats on the recent round — [company] is clearly in growth mode. Teams at this stage often prioritise [problem]..."
Step 1 — IF no strong signals:
[Fall back to persona-level pain without a specific event hook]
```
## Step 6 — Present the sequence [#step-6--present-the-sequence]
Output the complete sequence as a structured plan, each step clearly showing:
* Channel and timing
* Full message copy
* Which signal(s) it references
* Conditional variants where applicable
Offer to export as a format the user's sequencing tool (Outreach, Salesloft, Apollo, HubSpot sequences) can import.
# competitive-intel (/skills/reference/competitive-intel)
**Stage:** Market and persona intelligence
# Competitive Intel [#competitive-intel]
Use this skill to map who else is competing for the same buyer, how they're positioned,
and how to win deals when they come up. The output is a battle-card-ready competitive
overview your reps can use in active deals.
No CLI or special tools required. Works from web research alone.
## Input [#input]
Ask the user for:
* **Your product / company** — what you sell and who you sell to (or use ICP context from `extract-icp`)
* **Known competitors** *(optional)* — names the team already encounters in deals
* **Focus** *(optional)* — "we keep losing to X" or "we're entering a new segment and don't know the landscape"
## Step 1 — Identify the competitive set [#step-1--identify-the-competitive-set]
Research who else serves this buyer:
**Direct competitors** — same category, same buyer, similar price point
**Adjacent alternatives** — different category but competes for the same budget or mindset (e.g. "build it in-house", spreadsheets, agencies)
**Emerging entrants** — newer players gaining traction in the space
**Best sources:**
* G2 / Capterra "alternatives to \[your product]" pages
* "Compared to" mentions in G2 reviews of your product
* LinkedIn job postings at target companies — what tools do they list as requirements?
* Your own lost deal data — ask the user what names come up
* Product Hunt, Y Combinator recent batches (for emerging players)
## Step 2 — Profile each competitor [#step-2--profile-each-competitor]
For each competitor, research:
```
COMPETITOR — [Name]
Overview: [1–2 sentences: what they do and who they target]
Founded: [year / stage if known]
Pricing: [model and rough range — free tier, per-seat, usage-based, enterprise]
Target buyer: [who they sell to — title, company size, industry]
Key strengths: [what customers genuinely love — pull from G2 reviews]
Key weaknesses:[what customers complain about — pull from G2 reviews]
Positioning: [how they describe themselves — pull from homepage headline]
Typical deal: [SMB / mid-market / enterprise, sales-assisted / self-serve]
```
## Step 3 — Build battle cards [#step-3--build-battle-cards]
For each significant competitor, produce a battle card:
```
BATTLE CARD — [Competitor] vs [Your Product]
WHEN THEY COME UP
[In what deal stage or context does this competitor appear?]
[What does the buyer usually say when they bring them up?]
YOUR STRENGTHS IN THIS MATCHUP
1. [Specific advantage — back it with evidence if possible]
2. [...]
THEIR STRENGTHS (be honest)
1. [Where they genuinely win — don't dismiss this]
2. [...]
COMMON OBJECTIONS AND RESPONSES
"[Exact objection the prospect raises]"
→ [Response that acknowledges the concern, then reframes]
"[Another objection]"
→ [Response]
TRAP QUESTIONS TO ASK
(Questions that surface your advantages without sounding like a pitch)
- "[Question that reveals a gap in the competitor's approach]"
- "[Question that gets the buyer to articulate what matters to them]"
PROOF POINTS
- [Customer name / use case that directly counters competitor's strength]
- [Data point or quote that reinforces your position]
WHEN TO WALK AWAY
[Signs that this deal is firmly in the competitor's camp — save everyone's time]
```
## Step 4 — Competitive positioning summary [#step-4--competitive-positioning-summary]
Produce a one-page landscape overview:
```
COMPETITIVE LANDSCAPE — [Market / Category]
MARKET OVERVIEW
[2–3 sentences on the market: how mature, how consolidated, where it's heading]
COMPETITIVE TIERS
Tier 1 (direct / primary threats): [names]
Tier 2 (adjacent / indirect): [names]
Tier 3 (emerging / niche): [names]
YOUR POSITION
[Where you sit in this landscape — be specific, not just "we're the best"]
[What segment or use case you own most clearly]
[Where you're weakest relative to the field]
MARKET DYNAMICS TO WATCH
- [Trend or shift that could affect the competitive balance]
- [Emerging player worth tracking]
```
## Step 5 — Suggest next steps [#step-5--suggest-next-steps]
* Use battle cards in `deal-coaching` when a specific competitor comes up in a deal
* Feed "language competitors use" into `write-outreach` to differentiate messaging
* Add competitive positioning to the persona profile from `persona-research`
* Re-run this skill quarterly — competitive landscapes shift fast
# create-company-signals (/skills/reference/create-company-signals)
**Stage:** Signal activation
# Create Company Signals [#create-company-signals]
Use this skill to run company-level signal research against a list of target accounts.
The Saber CLI is the preferred path — it automates signal runs across your full list on a schedule.
Without it, this skill produces a research playbook you can run manually or via other tools.
## Before running signals (Saber CLI) [#before-running-signals-saber-cli]
If the CLI is available, check credits before executing any command that will consume them:
```bash
saber credits
```
Tell the user: "Running \[N] signals against \[M] companies will use \[N×M] credits." Ask them to confirm.
## Step 1 — Confirm signals and list [#step-1--confirm-signals-and-list]
From conversation context, confirm:
* The approved signal questions to activate (from `generate-signals` or `signal-discovery`)
* The account list to run them against
To find a list ID:
```bash
saber list company list
```
## Step 2 — Run signals [#step-2--run-signals]
***
### Path A — Saber CLI (full list, subscription mode) [#path-a--saber-cli-full-list-subscription-mode]
**Option 1 — Run once (recommended for getting started)**
```bash
saber subscription create \
--list \
--name "" \
--question "" \
--answer-type boolean \
--frequency monthly \
--run-once
```
Creates the subscription, runs it immediately, then stops the schedule.
**Option 2 — Recurring schedule**
```bash
saber subscription create \
--list \
--name "" \
--question "" \
--answer-type boolean \
--frequency weekly
```
Create one subscription per signal question.
**Spot-check a single company**
```bash
saber signal --domain --question "" --answer-type boolean
```
Use `--no-wait` to fire multiple signals in parallel:
```bash
saber signal --domain --question "" --no-wait
saber signal get
```
***
### Path B — No Saber CLI (research playbook) [#path-b--no-saber-cli-research-playbook]
Output a structured research playbook the user or their team can run manually or via other tools:
```
SIGNAL RESEARCH PLAYBOOK — [List Name]
For each company in the list, answer these questions:
Signal 1: [Question]
Category: [icp_fit / urgency / buying_signal]
Weight: [1–3]
How to find the answer:
- LinkedIn: [specific search or filter]
- Google: [suggested search string]
- Apollo: [filter to apply]
- News: [alert or keyword to track]
Signal 2: [Question]
[...]
TRACKING TEMPLATE
Use a spreadsheet with columns:
| Company | Domain | [Signal 1] | [Signal 2] | ... | Score | Notes |
```
Also suggest:
* **Google Alerts** for trigger-based signals (funding, leadership changes, expansion news)
* **Apollo or LinkedIn Sales Nav** filters for ICP-fit signals (headcount, tech stack, hiring)
* **HubSpot custom properties** to track signal results per company if HubSpot MCP is available
***
## Step 3 — Review results (Saber CLI) [#step-3--review-results-saber-cli]
```bash
saber subscription get
```
When complete, present results. Companies where signals fired positively are highest priority for outreach.
After reviewing results, use `score-accounts` to rank the full list.
## Key Saber commands [#key-saber-commands]
```bash
saber subscription create --list --name "" --question "" [--answer-type] [--frequency] [--run-once]
saber subscription trigger
saber subscription get
saber subscription list
saber subscription start
saber subscription stop
saber signal --domain --question "" [--answer-type] [--no-wait]
saber list company list
```
# create-contact-signals (/skills/reference/create-contact-signals)
**Stage:** Signal activation
# Create Contact Signals [#create-contact-signals]
Use this skill to run contact-level signal research using the Saber CLI.
## Saber CLI check [#saber-cli-check]
Before doing anything else, check if the Saber CLI is installed by running `saber --help`.
**If the CLI is available:** proceed with the full workflow below.
**If not installed:** inform the user that this skill requires the Saber CLI to execute signals (available at saber.app), then offer two options:
1. **Install the CLI** — once installed, come back and restart this skill
2. **Continue without the CLI** — document the approved signal questions and contact LinkedIn URLs so the user can run them later via the CLI; walk through the workflow conceptually so they know what to expect
## Prerequisites [#prerequisites]
* Approved contact signal definitions are available in conversation context (run `signal-discovery` first if not)
* Saber CLI is available (`saber --help` works)
## Before running signals [#before-running-signals]
Before executing any `saber signal` or `saber subscription` command that will consume credits:
1. Run `saber credits` and show the user their current balance.
2. Tell the user how many signals will be run (number of contacts × number of questions).
3. Ask the user to confirm they want to proceed before issuing the command.
## Two modes [#two-modes]
### Mode A — Spot-check a specific contact [#mode-a--spot-check-a-specific-contact]
Use `saber signal` with a LinkedIn profile URL to run a question against a specific contact:
```bash
saber signal --profile --question ""
```
This is synchronous by default — it waits for the result and prints it.
```bash
# Example
saber signal --profile https://linkedin.com/in/janedoe --question "Is this person posting about employee retention challenges?"
```
Use `--no-wait` to fire multiple signals without waiting:
```bash
saber signal --profile --question "" --no-wait
# Returns a signal ID; retrieve result later with:
saber signal get
```
### Mode B — Run signals across a large contact list (signal subscriptions) [#mode-b--run-signals-across-a-large-contact-list-signal-subscriptions]
For running signals across a large contact list, use signal subscriptions in the Saber dashboard or via the API. The CLI currently supports per-contact signals only.
## Workflow (spot-check mode) [#workflow-spot-check-mode]
### Step 1 — Pick contacts to check [#step-1--pick-contacts-to-check]
Ask the user which contacts they want to prioritise. You'll need LinkedIn profile URLs.
### Step 2 — Run signals [#step-2--run-signals]
For each contact and each approved signal question:
```bash
saber signal --profile --question "" --answer-type boolean
```
### Step 3 — Review and prioritise [#step-3--review-and-prioritise]
Present results to the user. Contacts where the signal fired positively should be sequenced first for outreach.
## Key commands [#key-commands]
```bash
saber signal --profile --question "" [--answer-type] [--no-wait]
saber signal get
```
# deal-coaching (/skills/reference/deal-coaching)
**Stage:** Pipeline and deal management
# Deal Coaching [#deal-coaching]
Use this skill to get a structured analysis of a specific deal. Bring deal context and
get back: a health assessment, risk flags, stakeholder map, and concrete next steps.
Works without any CLI. If the Saber CLI is available, it can run live signals on the
account to add real-time context.
## Input [#input]
Ask the user to describe the deal. Collect:
* **Company** — name, domain, size, industry
* **Contacts engaged** — names, titles, how active they've been
* **Deal stage** — where it sits in the pipeline
* **What's happened so far** — demos done, proposals sent, objections raised
* **Known blockers** — what's slowing things down
* **Timeline / deadline** — is there urgency? A budget cycle? A renewal date?
* **Competitive situation** — are competitors in the deal?
Collect missing context conversationally — don't dump all questions at once.
## Step 1 — Account research (optional enrichment) [#step-1--account-research-optional-enrichment]
If the Saber CLI is available and the domain is known, run a quick signal check:
```bash
saber signal --domain --question "Has this company hired a new [buyer title] in the last 6 months?" --yes
saber signal --domain --question "Is this company actively growing its [relevant team]?" --yes
```
If no CLI, use web search to check for recent news: funding, leadership changes, layoffs,
product launches, or any event that changes urgency or risk.
## Step 2 — Deal health assessment [#step-2--deal-health-assessment]
Score the deal across these dimensions:
**Champion** — Is there someone inside who wants this to happen?
* Strong: actively scheduling meetings, sharing internal context, coaching on the process
* Weak: hard to reach, not advocating internally, "I'll try to get others involved"
**Economic buyer** — Have you reached the person who controls budget?
* Strong: engaged in the process, has asked about pricing
* Weak: never spoken with, champion says "they'll rubber-stamp it"
**Pain** — Is the pain real, specific, and urgent?
* Strong: prospect has described a specific problem with a business impact
* Weak: "we'd be interested in seeing what this does"
**Timeline** — Is there a forcing function?
* Strong: renewal date, board commitment, end of budget cycle
* Weak: "sometime this year", "no rush"
**Competition** — What's the alternative if you don't win?
* Strong: you understand what else is being evaluated and why
* Weak: you don't know who else they're talking to
**Process** — Do you understand how they make decisions?
* Strong: you know the steps, who's involved, what happens next
* Weak: every meeting ends with "we'll get back to you"
Rate each dimension: **Strong / Weak / Unknown**
## Step 3 — Risk flags [#step-3--risk-flags]
Identify the top 2–3 risks in this deal:
```
RISK FLAGS
🔴 [Risk name]: [What's happening / not happening that signals this risk]
→ [What to do about it]
🟡 [Risk name]: [Watch area — not a crisis but worth monitoring]
→ [What to do about it]
```
Common risk patterns:
* **No champion** — you're selling to someone who can't say yes
* **Single-threaded** — only one contact, no access to the buying group
* **No urgency** — deal will drag forever without a forcing function
* **Ghosting** — responses are slowing down after an initial positive signal
* **Scope creep** — deal is growing but decision criteria keep shifting
* **Competitor late entry** — a new name just appeared in the last call
## Step 4 — Stakeholder map [#step-4--stakeholder-map]
Based on known contacts, map the buying committee:
```
STAKEHOLDER MAP
Champion: [Name] — [title] — [assessment: strong / developing / unclear]
Economic buyer: [Name or "not yet reached"]
Influencers: [Names, titles, stance if known]
Blockers: [Anyone who might slow or kill the deal]
Missing: [Roles you haven't reached yet that typically need to be involved]
```
## Step 5 — Recommended next steps [#step-5--recommended-next-steps]
Produce 3–5 specific, actionable next steps ranked by priority:
```
NEXT STEPS
1. [Specific action] — [Why this, why now]
How: [Exact ask or message to send]
2. [...]
3. [...]
```
Good next steps are:
* **Time-bound** — has a proposed date or deadline
* **Specific** — not "follow up" but "send the ROI template to \[Name] by \[date]"
* **Oriented toward advancing** — not just maintaining contact
## Step 6 — Suggested messaging [#step-6--suggested-messaging]
For the most important next step, draft the outreach:
* Email subject + body
* Or LinkedIn message
* Reference the deal context and most relevant signal or pain point
Offer to use `write-outreach` for a fuller personalisation pass if signal data is available.
# enrich-contacts (/skills/reference/enrich-contacts)
**Stage:** List building
# Enrich Contacts [#enrich-contacts]
Use this skill to look up email addresses and phone numbers for contacts in a Saber contact list, using whatever contact enrichment provider is available in the user's environment.
## Goal [#goal]
For each contact in a list, find their email address and/or phone number using an available enrichment provider, and surface those results to the user.
## Step 1 — Get the contact list [#step-1--get-the-contact-list]
**If the Saber CLI is available** (`saber --help` works):
Ask the user for the list ID, or help them find it:
```bash
saber list contact list
```
Then fetch the contacts:
```bash
saber list contact contacts
```
This returns contacts with name, LinkedIn URL, and any metadata Saber has. Note the names and LinkedIn URLs — these are what you'll pass to the enrichment provider.
**If the Saber CLI is not available:**
Ask the user to export the contact list as a CSV from the Saber dashboard and share it in the conversation. The CSV will contain contact names and LinkedIn URLs.
## Step 2 — Detect enrichment provider [#step-2--detect-enrichment-provider]
Check for available contact enrichment providers in the following order:
### 2a — MCP tools [#2a--mcp-tools]
Look through your available tools for any contact enrichment MCP servers. Providers that commonly offer MCP servers include:
* **Apollo** — look for tools with `apollo` in the name (e.g. people search, contact lookup)
* **Cognism** — look for tools with `cognism` in the name
* **Hunter.io** — look for tools with `hunter` in the name (email finder)
* **Clearbit** — look for tools with `clearbit` or `enrichment` in the name
* **Clay** — look for tools with `clay` in the name
* **Lusha** — look for tools with `lusha` in the name
* **ZoomInfo** — look for tools with `zoominfo` in the name
* **People Data Labs (PDL)** — look for tools with `pdl` or `people-data-labs` in the name
If any enrichment MCP tools are found, proceed to Step 3 using those tools.
### 2b — CLI tools [#2b--cli-tools]
If no MCP is detected, check for CLI tools:
```bash
apollo --help 2>/dev/null && echo "apollo found"
hunter --help 2>/dev/null && echo "hunter found"
pdl --help 2>/dev/null && echo "pdl found"
```
If a CLI is found, proceed to Step 3 using that CLI.
### 2c — No provider detected [#2c--no-provider-detected]
If neither an MCP nor a CLI is found, skip to **Step 4 — No provider found**.
## Step 3 — Enrich contacts [#step-3--enrich-contacts]
Once a provider is detected, enrich each contact. For each person in the list:
1. Use the provider's name + LinkedIn URL (preferred) or name + company to look up their contact info
2. Collect the results: email address, phone number, and any additional data the provider returns
3. Present results in a clear table as you go, or as a batch summary at the end
**Pacing:** If the list is longer than 20 contacts, ask the user if they want to process all at once or in batches. Some providers have rate limits — process sequentially if unsure.
**What to surface per contact:**
* Full name
* Email address (and confidence score if available)
* Phone number (if available)
* LinkedIn URL (echo back for reference)
**If a lookup fails** for a specific contact (no result found or an error), note it clearly and continue to the next.
## Step 4 — No provider found [#step-4--no-provider-found]
If no enrichment provider was detected, ask the user:
> "I couldn't find a contact enrichment provider in your environment. Do you have a service you use for finding contact information — such as Apollo, Cognism, Hunter.io, Clearbit, Clay, Lusha, or ZoomInfo?"
**If they name a provider**, help them connect it:
* **Apollo**: Apollo offers an MCP server and an API. Ask if they have an API key. If yes, guide them to install the Apollo MCP server so it's available in Claude Code. If no, direct them to apollo.io to sign up.
* **Cognism**: Ask if they have API access. Guide them to check whether a Cognism MCP server is available, or use their API directly with a script.
* **Hunter.io**: Has a simple REST API and CLI. Guide them to install the `hunter` CLI or call the API with `curl`.
* **Clay**: Clay is a data enrichment platform. Ask if they have a Clay table set up — they can export enriched data from Clay as CSV and share it in the conversation.
* **Other providers**: Ask if they have an API key. Offer to help write a simple script to call the provider's API for each contact.
**If they have no enrichment service**, explain the options:
> "To enrich contacts at scale you'll need a data provider. Here are the most common options:
>
> * **Apollo** (apollo.io) — good free tier, strong for B2B emails and LinkedIn enrichment
> * **Hunter.io** — focused on email finder, simple API, generous free tier
> * **Cognism** — enterprise-grade, strong in EMEA, phone numbers and GDPR compliance
> * **Clay** — flexible enrichment platform that chains multiple data sources
>
> Once you have a provider set up, come back to this skill and it will use it automatically."
## Output [#output]
Present the final results as a markdown table:
| Name | Email | Phone | LinkedIn |
| -------- | ------------------------------------------- | --------------- | ----------------------- |
| Jane Doe | [jane@example.com](mailto:jane@example.com) | +1 555 123 4567 | linkedin.com/in/janedoe |
| ... | | | |
If the user wants to import the enriched data back into Saber or another tool, offer to format the output as CSV.
# extract-icp (/skills/reference/extract-icp)
**Stage:** Signal foundations
# Extract ICP [#extract-icp]
Use this skill to research a company and produce a structured ICP that can be used as
the foundation for signal generation, list building, and outreach personalisation.
## Input [#input]
Ask the user for:
* **domain** — the company's website domain (e.g. `acme.com`)
* **context** *(optional)* — any targeting hint, e.g. "focus on NetSuite users" or "Series A SaaS only"
If the domain is already available from conversation context or from `saber org get`, use it directly and skip asking.
## Saber CLI check [#saber-cli-check]
Run `saber --help` to check if the Saber CLI is installed. If available, use it to supplement research:
```bash
saber signal --domain --question "Who are the target customers of this company?"
saber signal --domain --question "What pain points does this company's product solve?"
```
The CLI is optional — the skill works without it using web research.
## Step 1 — Research the company [#step-1--research-the-company]
Use web search, the company website, and any available tools to answer these questions:
```
1. What is their main product or service?
2. Who are their target customers? Describe the typical buyer.
3. What pain points do they solve? What were customers struggling with BEFORE buying?
4. What buying triggers lead companies to purchase? (e.g. new hire, funding round, expansion)
5. Name 5–10 customer companies they work with.
6. What is the typical customer company size? (employees and/or revenue)
7. What industries do their customers come from?
8. What is the buying committee? (who approves, who influences, who uses the product)
9. What is their pricing model and estimated deal size?
10. What is their sales motion? (self-serve / sales-assisted / enterprise)
```
**Best sources:**
* Company website — especially `/customers`, `/case-studies`, `/pricing`
* G2 / Capterra reviews — the "before" story in reviews is the most valuable
* LinkedIn job postings — the ideal candidate description often mirrors the buyer profile
* Case study PDFs and testimonials
* Saber CLI: `saber signal --domain --question "..."`
**What to look for in case studies and reviews:**
* The specific situation the customer was in *before* buying
* The exact words customers use to describe their problem
* What triggered them to look for a solution
## Step 2 — Extract the structured ICP [#step-2--extract-the-structured-icp]
Output in this format:
```
ICP — {domain}
PRODUCT
Summary: [1–2 sentence description of what the product does and who it's for]
TARGET COMPANY
Employee range: [min]–[max]
Stage: [startup / growth / enterprise / any]
Revenue range: [$X–$Y if known]
Industries: [2–5 specific industries, not broad categories like "tech"]
Geography: [countries or regions]
BUYING COMMITTEE
Decision makers: [job titles — e.g. "CFO", "VP of Engineering"]
Influencers: [titles that evaluate but don't approve]
End users: [titles who use the product daily]
DEAL ECONOMICS
Pricing model: [SaaS / usage / per-seat / project / other]
Estimated ACV: [$X–$Y or "unknown"]
Sales motion: [self-serve / sales-assisted / enterprise]
PAIN POINTS
(What customers LACKED before buying — use customer language when possible)
1. [specific pain point]
2. [...]
...
BUYING TRIGGERS
(Events that create urgency or prompt a search for solutions)
1. [trigger event]
2. [...]
...
EXISTING CUSTOMERS
(For lookalike targeting — list what you know)
- {Company}: {industry}, ~{headcount}, {country}
- [...]
```
## Key insight [#key-insight]
> **Pain points describe what customers LACKED before buying.**
> Good prospects ALSO lack these things right now.
This inverted framing is the foundation of good signal generation:
* Pain point: "No in-house marketing expertise" → prospect **doesn't have** a marketing team
* Pain point: "Manual reconciliation errors" → prospect **lacks** financial automation
* Pain point: "Reporting takes days to produce" → prospect has a **small** ops/finance team
Don't look for companies that solved the problem. Look for companies that still have it.
## Quality checks [#quality-checks]
Before finalising:
* [ ] Pain points use specific, observable language (not "inefficiency" — say "manual Excel-based reconciliation")
* [ ] Buying triggers are events, not attributes (not "growing company" — say "raised Series B" or "hired new CFO")
* [ ] Employee range is specific, not "SMB" or "mid-market"
* [ ] Industries are specific (not "technology" — say "SaaS", "fintech", "healthcare tech")
* [ ] Decision maker titles are singular and job-title-exact (not "finance leaders" — say "CFO", "Controller")
* [ ] At least 3 named existing customers listed with industry and headcount if known
## Next step [#next-step]
Once the ICP is confirmed, use the `generate-signals` skill to turn it into a scored signal set ready for Saber.
# find-expansion-accounts (/skills/reference/find-expansion-accounts)
**Stage:** Pipeline and deal management
# Find Expansion Accounts [#find-expansion-accounts]
Use this skill to surface upsell and cross-sell opportunities within your existing customer base
by running expansion-focused signals against a list of current accounts.
Works with or without the Saber CLI — the HubSpot MCP or a manual customer list are equally valid starting points.
## Step 1 — Get the customer list [#step-1--get-the-customer-list]
Ask the user how their customer data is available:
***
### Path A — Saber CLI [#path-a--saber-cli]
Run `saber --help` to confirm the CLI is installed.
```bash
saber list company list
```
Ask which list contains current customers. If a customer list doesn't exist yet, offer to create one.
***
### Path B — HubSpot MCP (if available) [#path-b--hubspot-mcp-if-available]
Pull customers directly using the HubSpot MCP:
```
Fetch HubSpot companies where lifecycle_stage = "customer"
```
Return company name and domain for each. If Saber CLI is available, create a Saber list from these domains. If not, work with the HubSpot data directly.
***
### Path C — Manual list [#path-c--manual-list]
Ask the user to provide a list of customer domains or company names. Work with that list directly.
***
## Step 2 — Define expansion signals [#step-2--define-expansion-signals]
Expansion signals indicate readiness to buy more or expand into adjacent products/seats.
Work with the user to define 3–5 expansion-specific signal questions.
**Growth signals** (they're scaling and may need more):
* "Is this company actively hiring in roles that would use our product?"
* "Has this company raised funding in the last 6 months?"
* "Is this company expanding into new markets or geographies?"
**Problem signals** (they're experiencing pain the expansion product solves):
* "Is this company posting about \[specific challenge your expansion product addresses]?"
* "Is this company hiring for a role that suggests they're outgrowing their current setup?"
**Intent signals** (they're researching adjacent solutions):
* "Is this company evaluating or recently adopted \[complementary tool]?"
**At-risk signals** (if the goal is retention, not just expansion):
* "Is this company reducing headcount or announcing layoffs?"
* "Is the original buyer at this company still in their role?"
If signal definitions already exist in conversation context, check whether they're relevant for expansion or whether new ones are needed.
## Step 3 — Run expansion signals [#step-3--run-expansion-signals]
### With Saber CLI [#with-saber-cli]
Check credits first:
```bash
saber credits
```
Tell the user: "Running \[N] signals against \[M] accounts will use \[N×M] credits." Ask them to confirm.
Then create subscriptions:
```bash
saber subscription create \
--list \
--name "" \
--question "" \
--answer-type boolean \
--frequency monthly \
--run-once
```
Create one subscription per signal question. Use `--run-once` to get results immediately.
### Without Saber CLI [#without-saber-cli]
For each customer and each expansion signal question, research manually or via available tools:
* Web search for recent news (funding, hiring, expansion, layoffs)
* HubSpot MCP: check custom properties or notes for relevant activity
* LinkedIn: check company updates and job postings
Record findings in a structured table:
```
| Company | Growth signal | Problem signal | Intent signal | At-risk signal |
|---------|--------------|----------------|---------------|----------------|
| Acme | ✓ Hiring | — | ✓ Eval HubSpot | — |
```
## Step 4 — Score and present expansion opportunities [#step-4--score-and-present-expansion-opportunities]
```
## Expansion Opportunities — [Customer List Name]
### High priority (2+ positive signals)
| Company | Domain | Signals | Top signal |
|---------|--------|---------|------------|
| Acme Corp | acme.com | 3/4 | Recently funded, hiring in sales |
### Watch list (1 positive signal)
| Company | Domain | Signal |
|---------|--------|--------|
| Beta Inc | beta.io | Expanding into EMEA |
### At-risk accounts
| Company | Domain | Risk signal |
|---------|--------|-------------|
| Gamma Ltd | gamma.io | Reducing headcount |
```
## Step 5 — Suggest next steps [#step-5--suggest-next-steps]
* **High priority accounts:** use `write-outreach` with expansion-focused messaging — reference the specific growth signal as the reason for reaching out
* **Watch list accounts:** re-run signals in 4 weeks to track movement
* **At-risk accounts:** flag for the account management team and consider a proactive check-in
* Use `deal-coaching` for any expansion account already in a conversation
# generate-signals (/skills/reference/generate-signals)
**Stage:** Signal foundations
# Generate Signals [#generate-signals]
Use this skill to turn a structured ICP into a full signal set — 12–15 research questions
that can be run against any list of prospect companies in Saber.
Run `extract-icp` first if you don't have a structured ICP yet.
## Input [#input]
Provide the ICP output from `extract-icp`, including:
* Pain points
* Buying triggers
* Target company profile (size, industry, stage)
* Optional: targeting context (e.g. "focus on NetSuite users")
## The core insight: inverse pain points [#the-core-insight-inverse-pain-points]
> **Pain points describe what customers LACKED before buying.**
> Good prospects ALSO lack these things right now.
When generating signals, look for companies that *currently have the problem* — not ones that already solved it.
| Pain point | What you're actually looking for |
| ----------------------------------------- | ------------------------------------------------- |
| "No in-house marketing expertise" | Companies WITHOUT a dedicated marketing team |
| "Manual reconciliation errors" | Companies that lack financial automation |
| "Reporting takes days to produce" | Companies using spreadsheets for reporting |
| "Rapid headcount growth overwhelming ops" | Companies with 30%+ headcount growth in 12 months |
## Signal categories [#signal-categories]
Generate signals across three categories:
### `icp_fit` (4–6 signals) [#icp_fit-46-signals]
Tests whether the company profile matches the ICP target.
* Employee count, industry, stage, geography, tech stack requirements
* These establish baseline fit — wrong profile = wrong prospect regardless of timing
### `urgency` (3–5 signals) [#urgency-35-signals]
Tests whether a recent event creates buying pressure.
* **Must include explicit time bounds** — e.g. "in the last 6 months", "in the last 9 months"
* Never phrase as "Has the company ever..." — that returns stale data
* Based on buying triggers from the ICP
* A missing urgency signal is not a disqualifier — it means "right fit, wrong time" (−15 points)
### `buying_signal` (4–5 signals) [#buying_signal-45-signals]
Tests whether the company shows evidence of the pain point.
* Derived from ICP pain points using the inverse framing above
* A missing buying signal means no evidence of pain → likely a poor fit
* **Never set as a disqualifier** — buying signals are bonuses, not hard requirements
## Signal format [#signal-format]
Output each signal in this format:
```
Signal {N} — {category}
Question: {the exact research question to ask about a prospect company}
AnswerType: {boolean | number | open_text}
DerivedFrom: {pain_point | buying_trigger | icp_constraint}
SourceText: {the exact pain point or trigger this is based on}
Rationale: {why this signal identifies a good prospect}
Interpretation:
[For boolean]
positiveAnswer: true / false (which answer indicates a fit?)
[For number]
numberRange: { min: X, max: Y }
higherIsBetter: true / false (if outside range, which direction is better?)
[For open_text]
positiveKeywords: ["word1", "word2", ...]
negativeKeywords: ["word1", "word2", ...]
[All signals]
isDisqualifier: true / false (wrong answer → prospect score = 0?)
weight: 1 / 2 / 3 (1 = nice-to-have, 2 = important, 3 = critical)
```
## Disqualifier rules [#disqualifier-rules]
**When to set `isDisqualifier: true`:**
* The product only works with a specific tech stack (requires NetSuite, Salesforce, AWS)
* The product is only relevant to a specific industry or geography
* The prospect would be a direct competitor
**When to NEVER set `isDisqualifier: true`:**
* `buying_signal` category — these are bonuses, never gatekeepers
* `urgency` category — timing signals never eliminate a prospect
* Any signal based on a "nice-to-have" criterion
## Weight guide [#weight-guide]
| Weight | Use when |
| -------------------- | ------------------------------------------------------------------ |
| **3 — critical** | Core fit requirement or strongest urgency signal (max 2–3 signals) |
| **2 — important** | Most signals should be here |
| **1 — nice-to-have** | Supplementary signals, low-confidence proxies |
## Scoring algorithm [#scoring-algorithm]
Once signals are run against a prospect:
```
1. For each signal, determine if the answer is "positive" per interpretation rules
2. earnedWeight = sum of weights for positive-answer signals
3. totalWeight = sum of all signal weights
4. rawScore = (earnedWeight / totalWeight) × 100
5. Apply penalties:
— Any disqualifier fires wrong → score = 0 immediately
— Zero buying_signal hits → score = 0 (no pain evidence)
— Zero urgency hits → score − 15 (right fit, wrong time)
6. Final score: 0–100
```
**Thresholds:**
* **70+** — High fit, prioritise outreach
* **50–69** — Moderate fit, worth pursuing
* **30–49** — Low fit, monitor for triggers
* **\< 30** — Poor fit, deprioritise
## Saber CLI — activating signals [#saber-cli--activating-signals]
Once the signal set is approved, use `create-company-signals` to activate them in Saber:
```bash
saber subscription create \
--list \
--name "" \
--question "" \
--answer-type boolean \
--frequency weekly
```
Run one `subscription create` command per signal. The signal metadata (weight, category, interpretation rules) should be kept in conversation context for use by `score-accounts`.
## Quality checks [#quality-checks]
Before finalising:
* [ ] Every urgency signal has an explicit time bound ("in the last 6 months" — not "recently" or "ever")
* [ ] No `buying_signal` has `isDisqualifier: true`
* [ ] No `urgency` signal has `isDisqualifier: true`
* [ ] At most 2–3 signals have `weight: 3` (reserve for truly critical signals)
* [ ] Each signal maps back to a specific pain point, trigger, or ICP constraint
* [ ] `open_text` signals have both `positiveKeywords` and `negativeKeywords` defined
* [ ] The signal set covers all three categories: `icp_fit`, `urgency`, `buying_signal`
* [ ] At least one signal uses the inverse pain point framing (looking for absence of a capability)
## Next steps [#next-steps]
* Use `create-company-signals` to activate the signals against a Saber list
* After signals run, use `score-accounts` to rank accounts using the weighted scoring model above
# import-from-hubspot (/skills/reference/import-from-hubspot)
**Stage:** CRM
# Import from HubSpot [#import-from-hubspot]
Use this skill to pull companies or contacts from HubSpot into Saber as a list, so you can run signals against your existing CRM data.
## HubSpot MCP check [#hubspot-mcp-check]
Before doing anything else, check if a HubSpot MCP is available by looking for HubSpot-related tools in your available tool list (tools with `hubspot` in the name for reading contacts, companies, or lists).
**If a HubSpot MCP is available:** proceed with the full workflow below.
**If no HubSpot MCP is detected:** inform the user and offer two options:
1. **Set up the HubSpot MCP** — guide them to install the HubSpot MCP server for Claude Code
2. **Manual CSV import** — ask the user to export their HubSpot list as a CSV and share it; then use `saber list company import` or `saber list contact create` to bring it into Saber
## Saber CLI check [#saber-cli-check]
Also verify the Saber CLI is available (`saber --help`). It is needed to create the Saber list.
**If not available:** inform the user and direct them to saber.app.
## Step 1 — Identify the HubSpot data to import [#step-1--identify-the-hubspot-data-to-import]
Ask the user what they want to import:
* A specific HubSpot **list** (static or active)?
* A **view** or **segment** filtered by properties (e.g. industry, lifecycle stage, owner)?
* All companies/contacts matching certain criteria?
Use the HubSpot MCP to browse available lists if the user isn't sure:
```
List all HubSpot company lists / contact lists
```
## Step 2 — Pull the records from HubSpot [#step-2--pull-the-records-from-hubspot]
Using the HubSpot MCP, fetch the companies or contacts from the selected list/view. For each record, collect:
**For companies:**
* Company name
* Domain / website
* Industry, size, country (if available)
* HubSpot company ID (for reference)
**For contacts:**
* Full name
* LinkedIn URL (if available — preferred for Saber)
* Email address
* Company name
* HubSpot contact ID
Paginate through the full result set if the list is large.
## Step 3 — Create the Saber list [#step-3--create-the-saber-list]
**For a company list:**
```bash
saber list company create --name ""
```
If the companies have consistent attributes (industry, size, country), consider using those as filters instead so Saber can also discover similar companies:
```bash
saber list company create --name "" --industry "" --country "" --size ""
```
**For a contact list:**
Create the list using the LinkedIn URLs from HubSpot:
```bash
saber list contact create --name "" \
--company-linkedin \
--company-linkedin \
[--title ""]
```
## Step 4 — Confirm import [#step-4--confirm-import]
Show the user a summary:
* How many records were pulled from HubSpot
* How many were successfully added to Saber
* Any records that couldn't be matched (e.g. contacts without a LinkedIn URL or domain)
```bash
saber list company get
# or
saber list contact get
```
## Step 5 — Suggest next steps [#step-5--suggest-next-steps]
Once the list is in Saber:
* Run `signal-discovery` if signal definitions don't exist yet
* Run `create-company-signals` or `create-contact-signals` to activate signal tracking against the imported list
* Run `score-accounts` after signals complete to prioritise the imported accounts
# Skill Reference (/skills/reference)
## List building [#list-building]
* [`build-account-list`](/skills/reference/build-account-list) — Build a target account list and run company signals against it. Works with the Saber CLI, Apollo, HubSpot, or any available prospecting tool — with a manual fallback.
* [`build-contact-list`](/skills/reference/build-contact-list) — Build a target contact list and run contact-level signals against them. Works with the Saber CLI, Apollo, LinkedIn, HubSpot, or manual research.
* [`enrich-contacts`](/skills/reference/enrich-contacts) — Find and add contact information (email, phone) to a Saber contact list using available enrichment providers such as Apollo or Cognism. Detects available MCPs and CLIs automatically.
## Outreach [#outreach]
* [`build-sequence`](/skills/reference/build-sequence) — Design a multi-step outreach sequence (email + LinkedIn) with messages personalised using Saber signal results and timing tied to signal triggers.
* [`objection-handler`](/skills/reference/objection-handler) — Build or extend an objection library for your ICP — common objections mapped to the real concern underneath, with response frameworks your team can use in live deals.
* [`write-outreach`](/skills/reference/write-outreach) — Write personalised cold outreach — email and LinkedIn — using Saber signal results and contact data as personalisation context.
## Market and persona intelligence [#market-and-persona-intelligence]
* [`competitive-intel`](/skills/reference/competitive-intel) — Map the competitive landscape for a target market — identify competitors, compare positioning, surface common objections, and build battle cards for your sales team.
* [`market-map`](/skills/reference/market-map) — Map a target market — size, segments, key players, adjacent verticals, and entry points. Helps GTM teams understand the landscape before building lists or running signals.
* [`persona-research`](/skills/reference/persona-research) — Build a deep profile of a buying persona — motivations, challenges, buying behaviour, language they use, and where to find them. Helps reps understand who they're selling to before they reach out.
## Signal activation [#signal-activation]
* [`create-company-signals`](/skills/reference/create-company-signals) — Activate company-level signal tracking. Runs signals via the Saber CLI if available; without it, outputs a research playbook and suggests alternative tracking methods.
* [`create-contact-signals`](/skills/reference/create-contact-signals) — Activate contact-level signal tracking using the Saber CLI — creates signals for contacts in a contact list.
* [`manage-signals`](/skills/reference/manage-signals) — View, pause, resume, and adjust your active Saber signal subscriptions.
## Pipeline and deal management [#pipeline-and-deal-management]
* [`deal-coaching`](/skills/reference/deal-coaching) — Analyse a deal in progress — assess health, identify risks, map the buying committee, and recommend specific next steps to move it forward.
* [`find-expansion-accounts`](/skills/reference/find-expansion-accounts) — Identify existing customers showing buying signals for upsell or cross-sell opportunities. Works with Saber, HubSpot, or any available customer data source.
* [`pipeline-review`](/skills/reference/pipeline-review) — Audit pipeline health from CRM data — identify at-risk deals, surface stalled opportunities, flag forecast risk, and recommend where to focus.
## Signal foundations [#signal-foundations]
* [`extract-icp`](/skills/reference/extract-icp) — Research a company domain and extract a structured Ideal Customer Profile (ICP) — including target company profile, buying committee, pain points, buying triggers, and existing customers.
* [`generate-signals`](/skills/reference/generate-signals) — Generate 12–15 structured, weighted research signals from a structured ICP, ready to activate in Saber. Each signal includes answer type, weight, and interpretation rules for automatic scoring.
* [`signal-discovery`](/skills/reference/signal-discovery) — Guided entry point for defining your ICP and buying signals — runs extract-icp then generate-signals in sequence. Start here before creating signals or building lists.
## CRM [#crm]
* [`import-from-hubspot`](/skills/reference/import-from-hubspot) — Pull a HubSpot company list, view, or segment into Saber as a target account or contact list, ready for signal activation.
* [`sync-to-hubspot`](/skills/reference/sync-to-hubspot) — Push Saber signal results back to HubSpot as contact or company properties, keeping your CRM up to date with the latest intent data.
## Research and qualification [#research-and-qualification]
* [`qualify-inbound`](/skills/reference/qualify-inbound) — Qualify an inbound lead by running your defined signals against their company domain and producing a scored qualification summary.
* [`research-account`](/skills/reference/research-account) — Deep research on a single company — signals, hiring, news, tech stack, and LinkedIn presence. Uses the Saber CLI for signal data and any available MCP tools for broader research.
* [`score-accounts`](/skills/reference/score-accounts) — Score and rank a list of accounts by signal strength to surface the highest-priority targets for outreach. Works with Saber signal results, Apollo data, HubSpot properties, or any pasted signal data.
# manage-signals (/skills/reference/manage-signals)
**Stage:** Signal activation
# Manage Signals [#manage-signals]
Use this skill to get an overview of your active signal subscriptions and manage them — pause ones that aren't useful, resume paused ones, adjust frequency, or clean up old subscriptions.
## Saber CLI check [#saber-cli-check]
Before doing anything else, check if the Saber CLI is installed by running `saber --help`.
**If not installed:** inform the user that managing signals requires the Saber CLI (available at saber.app).
## Step 1 — Fetch all subscriptions [#step-1--fetch-all-subscriptions]
```bash
saber subscription list
```
Present the results in a clear table:
| ID | Name | List | Frequency | Status | Last run |
| --- | ------------- | --------------- | --------- | ------ | ---------- |
| abc | Hiring signal | Tier 1 Accounts | weekly | active | 2026-04-08 |
| ... | | | | | |
Group by status: **active**, **paused**, **completed** (run-once subscriptions that finished).
## Step 2 — Ask what the user wants to do [#step-2--ask-what-the-user-wants-to-do]
Present the options:
1. **View results** for a specific subscription
2. **Pause** one or more subscriptions
3. **Resume** a paused subscription
4. **Trigger** a subscription to run now (outside its normal schedule)
5. **Stop** a subscription permanently
6. **Review all results** — surface a summary across all active subscriptions
## Step 3 — Execute the chosen action [#step-3--execute-the-chosen-action]
### View results [#view-results]
```bash
saber subscription get
```
Present results clearly. If the signal is a boolean, show what percentage of companies returned positive.
### Pause [#pause]
```bash
saber subscription stop
```
Note: `stop` pauses the schedule but keeps the subscription and its results intact.
### Resume [#resume]
```bash
saber subscription start
```
### Trigger now [#trigger-now]
```bash
saber subscription trigger
```
Useful for running a subscription ahead of schedule (e.g. before a sales review).
### Stop permanently [#stop-permanently]
Confirm with the user before stopping:
> "Stopping permanently will cancel the subscription. Results already collected are preserved. Are you sure?"
```bash
saber subscription stop
```
## Step 4 — Review mode (optional) [#step-4--review-mode-optional]
If the user chooses "Review all results", fetch and summarise results across all active subscriptions:
```bash
saber subscription get # for each active subscription
```
For each subscription, report:
* How many companies were checked
* What percentage returned positive
* When it last ran and when it runs next
Highlight subscriptions where:
* A high percentage of companies are positive (strong signal, prioritise outreach)
* No companies are positive (signal may need rewording or the list may need refreshing)
* The last run was more than 30 days ago (may be stale)
Suggest next steps based on the review, such as using `score-accounts` to rank accounts by combined signal strength.
# market-map (/skills/reference/market-map)
**Stage:** Market and persona intelligence
# Market Map [#market-map]
Use this skill to build a structured map of a target market before you start prospecting
into it. The output gives you a clear picture of who's in the market, how it's segmented,
and where your best entry points are.
No CLI or special tools required. Works from web research alone. If the Saber CLI is
available, it can enrich the output with signal data on key segments.
## Input [#input]
Ask the user for:
* **Market or category** — e.g. "B2B SaaS companies selling to mid-market HR teams in Europe"
* **ICP context** *(optional)* — pull from `extract-icp` if available
* **Intent** — what are they mapping this market for? (entering a new segment, building a TAM analysis, prioritising territories)
## Step 1 — Size and shape the market [#step-1--size-and-shape-the-market]
Research to answer:
* How many companies roughly fit the target profile?
* What's the estimated TAM / SAM for this segment?
* Is this market growing, stable, or contracting?
* Is it early-stage (fragmented, low awareness) or mature (consolidated, competitive)?
* What geography does this market span?
**Best sources:**
* Industry analyst reports (Gartner, Forrester, IDC summaries)
* G2 category pages (number of products, number of reviews = market maturity signal)
* LinkedIn: search the target company type and check result counts
* YC, Crunchbase for funding activity as a growth proxy
## Step 2 — Identify and define segments [#step-2--identify-and-define-segments]
Break the market into meaningful segments — groups of companies that behave similarly
and are best reached the same way:
```
SEGMENTS
Segment 1 — [Name]
Description: [Who they are]
Size: [Estimated number of companies / percentage of market]
Characteristics: [What makes this segment distinct — size, geography, tech stack, growth stage]
Example companies: [3–5 real company names]
Entry difficulty: [Easy / Medium / Hard — and why]
Segment 2 — [...]
```
Segment on dimensions that matter for GTM, not just firmographics:
* **By buyer** — who makes the decision? (e.g. CTO-led vs RevOps-led vs HR-led)
* **By stage** — early-stage companies behave differently from scale-ups
* **By tech stack** — already using \[tool] vs greenfield
* **By motion** — product-led vs sales-led; inbound-heavy vs outbound-heavy
* **By geography** — regulatory, cultural, and competitive differences that affect buying
## Step 3 — Map key players [#step-3--map-key-players]
For each segment, identify the notable companies (targets, not competitors):
```
KEY PLAYERS BY SEGMENT
[Segment 1]
Large (500+ employees): [company names, countries, brief description]
Mid-market (51–500): [company names]
Small (11–50): [company names]
```
If the Saber CLI is available, build a list and run signals:
```bash
saber list company create --name "[Segment 1]" --industry "" --country "" --size ""
```
## Step 4 — Adjacent verticals [#step-4--adjacent-verticals]
Identify markets that are adjacent to the core target — either as expansion targets
or as sources of lookalike companies:
```
ADJACENT VERTICALS
[Adjacent market 1]:
Similarity to core: [Why this is adjacent]
Differences: [What's different — buyer, tech stack, compliance, motion]
Opportunity: [Worth pursuing now / worth watching / out of scope]
[...]
```
## Step 5 — Entry point recommendations [#step-5--entry-point-recommendations]
Based on the map, recommend where to start:
```
RECOMMENDED ENTRY POINTS
Best first segment: [Name]
Why: [Highest density of ICP-fit companies / easiest to reach / least competitive]
Second priority: [Name]
Why: [...]
Hold for now: [Name]
Why: [Too early / too competitive / needs a reference customer first]
SUGGESTED FIRST LIST
[Segment, filters, estimated size]
→ Use `build-account-list` to activate this segment in Saber
```
## Step 6 — Suggest next steps [#step-6--suggest-next-steps]
* Run `build-account-list` to activate the highest-priority segment
* Run `signal-discovery` to define signals specific to this market's buying triggers
* Run `competitive-intel` to understand who else is selling into this space
* Re-run this skill when entering a new geography or vertical
# objection-handler (/skills/reference/objection-handler)
**Stage:** Outreach
# Objection Handler [#objection-handler]
Use this skill to build a structured objection library tailored to your ICP and product.
Each entry surfaces the real concern behind the objection and gives reps a response
framework — not a script, but a way to think through it.
No CLI or special tools required.
## Input [#input]
Ask the user for:
* **ICP and product context** — who you sell to and what you sell (or pull from `extract-icp` if available)
* **Known objections** *(optional)* — objections the team already hears; if provided, start there
* **Focus area** *(optional)* — "budget objections", "timing objections", "we're already using X"
If no specific objections are provided, generate a set from first principles based on the ICP.
## Step 1 — Identify the objection set [#step-1--identify-the-objection-set]
Common objection categories for B2B sales:
**Budget and cost**
* "It's too expensive" / "We don't have budget right now"
* "We need to see ROI before we can commit"
* "We're in a budget freeze"
**Timing**
* "Not a priority right now" / "Come back in Q3"
* "We're in the middle of \[initiative] — bad timing"
* "Let's revisit after \[event]"
**Competitive / status quo**
* "We're already using \[competitor]"
* "We built something internally"
* "We do this with spreadsheets / manually and it works fine"
**Trust and risk**
* "We've never heard of you" / "You're too small / new"
* "We had a bad experience with a similar tool"
* "What happens to our data?"
**Internal process**
* "I need to get buy-in from \[stakeholder]"
* "I'm not the decision maker"
* "IT / Legal / Security needs to review this first"
**Product fit**
* "We're too small / too big for this"
* "It doesn't integrate with \[tool we use]"
* "We don't need all of this — it's overkill"
Ask the user which categories to prioritise, or cover all if no preference given.
## Step 2 — Build the objection library [#step-2--build-the-objection-library]
For each objection, produce an entry in this format:
```
OBJECTION — "[Exact words the prospect uses]"
REAL CONCERN
[What this objection usually means underneath — what fear or constraint is driving it?]
[Is it a real objection or a brush-off? How to tell the difference.]
RESPONSE FRAMEWORK
1. Acknowledge — [how to validate the concern without agreeing it's a dealbreaker]
2. Clarify — [question to ask that gets to the real concern underneath]
3. Reframe — [how to shift the frame without being defensive]
4. Evidence — [what proof point, story, or data point is most relevant here]
5. Next step — [what to ask for at the end of the response]
EXAMPLE RESPONSE
"[A natural-sounding response that follows the framework — not a script, a model]"
SIGNALS THAT IT'S A REAL OBJECTION VS A BRUSH-OFF
Real: [what the prospect says or does that means this is a genuine blocker]
Brush-off: [what signals this isn't the real issue]
WHAT NOT TO SAY
- [Response that commonly backfires with this persona]
- [Trap that reps often fall into]
```
## Step 3 — Signal-informed variants (optional) [#step-3--signal-informed-variants-optional]
If signal data is available (from Saber or any source), add variants for specific contexts:
```
SIGNAL-INFORMED VARIANT — "[Objection]"
IF prospect recently raised funding:
→ [How the response changes — funding reframes the budget conversation]
IF prospect hired a new [title] in the last 6 months:
→ [How the response changes — new leadership = open evaluation window]
IF prospect is actively hiring in [relevant role]:
→ [How the response changes — growth signals urgency]
```
## Step 4 — Output the full library [#step-4--output-the-full-library]
Present the objection library as a structured, shareable reference. Offer to:
* Format it as a Notion table or Google Doc outline
* Export as markdown the team can add to their sales playbook
* Prioritise the top 5 objections for a quick-reference card
## Step 5 — Suggest next steps [#step-5--suggest-next-steps]
* Add the most common objections to `build-sequence` — proactively address them in later touchpoints
* Use the "real concern" framing in `write-outreach` to acknowledge pain without sounding salesy
* Combine with `competitive-intel` battle cards for objections involving competitors
* Re-run this skill when entering a new segment or after a batch of lost deals
# persona-research (/skills/reference/persona-research)
**Stage:** Market and persona intelligence
# Persona Research [#persona-research]
Use this skill to build a rich profile of a target buyer persona. The output gives reps
the context they need to engage the right person with the right message — regardless of
what tools you use for prospecting.
No CLI or special tools required. Works from web research alone.
## Input [#input]
Ask the user for:
* **Job title / persona** — e.g. "Head of RevOps", "VP of Sales", "CFO at a Series B SaaS"
* **Company profile** — the type of company this persona works at (size, industry, stage)
* **Context** *(optional)* — any known information about this persona from deals or calls
If ICP context is already available from `signal-discovery` or `extract-icp`, use the buying
committee from there and skip asking.
## Step 1 — Research the persona [#step-1--research-the-persona]
Use web search and any available tools to answer these questions:
**Role and context**
* What does this person own? What are they measured on?
* Who do they report to? Who do they manage?
* What does success in this role look like at the end of the year?
**Day-to-day reality**
* What does a typical week look like for them?
* What meetings do they run or attend?
* What decisions are they responsible for?
**Challenges and frustrations**
* What slows them down?
* What problems keep coming up that they haven't solved?
* What did the job posting say about the challenges this role will tackle?
**Buying behaviour**
* Do they initiate purchases or respond to internal requests?
* How do they evaluate new tools? (trials, demos, peer recommendations, analyst reports)
* Who else is involved in the decision? Do they control budget?
* How long do decisions typically take at their company stage?
**Language and content**
* What words and phrases do they use to describe their problems? (pull from reviews, LinkedIn posts, conference talks)
* What content do they consume? (newsletters, podcasts, communities, conferences)
* What topics do they post about?
**Best sources:**
* LinkedIn job postings for this title — the "you will" and "you have" sections describe the person
* G2 / Capterra reviews written by this persona — the "pros/cons" language is gold
* LinkedIn posts from people in this role
* Conference session titles and speaker bios
* Substack / newsletter bylines
## Step 2 — If Saber CLI is available (optional enrichment) [#step-2--if-saber-cli-is-available-optional-enrichment]
Run contact-level signals on specific people in this role:
```bash
saber signal --profile --question "Is this person posting about [relevant challenge] recently?"
saber signal --profile --question "Has this person been in their current role for less than 12 months?"
```
## Step 3 — Output the persona profile [#step-3--output-the-persona-profile]
```
PERSONA — [Title] at [Company Type]
ROLE CONTEXT
Reports to: [who they typically report to]
Manages: [team / function / budget they own]
Key metrics: [what they're measured on — e.g. pipeline generated, quota attainment, CAC]
DAY-TO-DAY
[2–3 sentences describing what they actually spend their time on]
GOALS
Short-term: [what they're trying to accomplish this quarter]
Long-term: [what career or company outcomes they're working toward]
CHALLENGES
1. [Specific, observable challenge — use their language]
2. [...]
3. [...]
BUYING BEHAVIOUR
Initiator: [Do they start the search or respond to one?]
Evaluation: [How do they assess vendors — trials, demos, peer refs, G2?]
Decision power: [Do they own budget? Who else signs off?]
Timeline: [How long does a decision typically take at their stage?]
LANGUAGE THEY USE
[Exact phrases pulled from reviews, posts, and job descriptions — not paraphrased]
- "[quote from a G2 review or LinkedIn post]"
- "[another phrase they use]"
WHERE TO FIND THEM
Online: [communities, Slack groups, subreddits, LinkedIn groups]
Content: [newsletters, podcasts, blogs they read]
Events: [conferences or meetups they attend]
WHAT TO AVOID
- [Common mistake reps make with this persona]
- [Messaging that lands poorly — e.g. features they don't care about]
- [Tone or approach that feels off]
```
## Step 4 — Suggest next steps [#step-4--suggest-next-steps]
* Use this profile to guide messaging in `write-outreach` or `build-sequence`
* Use the "language they use" section when writing signal questions in `generate-signals`
* Share with the team as a reference doc before a campaign kicks off
# pipeline-review (/skills/reference/pipeline-review)
**Stage:** Pipeline and deal management
# Pipeline Review [#pipeline-review]
Use this skill to get a structured health check on your pipeline. Bring your deal data
and get back: a coverage assessment, at-risk deal flags, stall analysis, and a recommended
focus list for the week.
Works with HubSpot MCP (if connected), pasted deal data, or a CSV export from your CRM.
No Saber CLI required.
## Input [#input]
Ask the user how they want to provide pipeline data:
**Option A — HubSpot MCP (if available)**
Check for HubSpot tools in the available tool list. If connected, pull deals directly:
```
Fetch all open deals from HubSpot, including: deal name, company, stage, amount,
close date, last activity date, owner, and any custom signal properties.
```
**Option B — Paste or describe**
Ask the user to paste a pipeline export or describe their open deals. Minimum useful fields:
* Company name
* Deal stage
* Deal value / ACV
* Expected close date
* Last activity date
* Owner
**Option C — CSV**
If the user uploads a CSV, parse it and proceed with whatever fields are present.
## Step 1 — Pipeline overview [#step-1--pipeline-overview]
Calculate and present:
```
PIPELINE OVERVIEW
Total open pipeline: $[X]
Deal count: [N] deals across [N] reps
Average deal size: $[X]
Weighted pipeline: $[X] (stage-weighted by typical conversion rates)
BY STAGE
[Stage name]: [N] deals, $[X] value, avg [N] days in stage
[...]
COVERAGE
Quota this period: $[X]
Pipeline: $[X]
Coverage ratio: [X]x
[Flag if below 3x]
```
## Step 2 — At-risk deals [#step-2--at-risk-deals]
Flag deals showing risk signals:
**Stalled** — in the same stage for longer than typical
**No recent activity** — no logged activity in 14+ days
**Slipping close date** — close date has moved more than once
**Single-threaded** — only one contact on the deal
**Missing next step** — no scheduled next activity
**Late-stage with no economic buyer** — in proposal/negotiation but no exec engaged
```
AT-RISK DEALS
🔴 [Company] — $[X] — [Stage]
Risk: [Stalled 28 days / No activity since [date] / Close date moved 3x]
Owner: [Name]
Recommended action: [Specific suggested next step]
🟡 [Company] — $[X] — [Stage]
Risk: [Watch area]
[...]
```
## Step 3 — Forecast analysis [#step-3--forecast-analysis]
Assess commit and best-case forecast:
```
FORECAST
Commit (high confidence): $[X] — [N] deals
Best case (likely to close): $[X] — [N] deals
Pipeline (needs work): $[X] — [N] deals
FORECAST RISK
[Deal name]: $[X] — [reason this might not close as forecast]
[...]
GAP TO QUOTA
[If commit covers quota: "Commit covers quota — focus on best-case upside"]
[If gap exists: "Gap of $[X] — need to close [N] deals from best-case or pull something forward"]
```
## Step 4 — Where to focus [#step-4--where-to-focus]
Recommend the top 5 deals to prioritise this week, with reasoning:
```
FOCUS LIST — This Week
1. [Company] — $[X] — [Stage]
Why: [Highest value deal with a clear path to close / decision expected this week / at risk of stalling]
Next step: [Specific action]
2. [...]
```
## Step 5 — Pipeline health score [#step-5--pipeline-health-score]
Rate overall pipeline health:
* **Coverage** — is there enough pipeline to hit quota?
* **Velocity** — are deals moving through stages at a healthy pace?
* **Distribution** — is pipeline too concentrated in one stage or one rep?
* **Quality** — are deals well-qualified or are there a lot of early-stage unknowns?
```
PIPELINE HEALTH
Coverage: [Green / Yellow / Red] — [X]x coverage
Velocity: [Green / Yellow / Red] — avg [N] days per stage
Distribution: [Green / Yellow / Red] — [observation]
Quality: [Green / Yellow / Red] — [observation]
OVERALL: [Healthy / Needs attention / At risk]
```
## Step 6 — Suggest next steps [#step-6--suggest-next-steps]
* Use `deal-coaching` on the top 2–3 at-risk deals for deeper analysis
* Use `write-outreach` to re-engage stalled deals with a signal-informed message
* Use `find-expansion-accounts` to identify upsell opportunities in the existing customer base
* Schedule a follow-up pipeline review in 2 weeks to track movement
# qualify-inbound (/skills/reference/qualify-inbound)
**Stage:** Research and qualification
# Qualify Inbound [#qualify-inbound]
Use this skill to quickly qualify an inbound lead by running your active signal definitions against their company domain and scoring the result.
## Saber CLI check [#saber-cli-check]
Before doing anything else, check if the Saber CLI is installed by running `saber --help`.
**If not installed:** inform the user that qualifying inbound leads requires the Saber CLI (available at saber.app). Offer to continue without it by asking the user to describe the lead and manually assessing fit against their ICP from conversation context.
## Step 1 — Get the lead [#step-1--get-the-lead]
Ask for the inbound company's domain (e.g. `acme.com`). If a company name is provided instead, ask for the domain or attempt to infer it.
## Step 2 — Load signal definitions [#step-2--load-signal-definitions]
Check for approved signal definitions in conversation context. If none are available, ask the user:
> "Do you have defined signal questions for your ICP? If not, run `signal-discovery` first to set them up."
If the user has signals defined (even informally, e.g. "we care about companies hiring in sales"), proceed with those.
## Step 3 — Check for existing signal data [#step-3--check-for-existing-signal-data]
Before running new signals (which costs credits), check if signals have already been run for this domain:
```bash
saber subscription list
```
Scan the results for any subscription that might have already processed this company. If results exist, use them and skip to Step 5.
## Step 4 — Run signals [#step-4--run-signals]
Show the user their credit balance first:
```bash
saber credits
```
Tell them: "Running \[N] signals against \[domain] will use \[N] credits." Ask them to confirm.
Then run each signal question against the domain:
```bash
saber signal --domain --question "" --answer-type boolean
```
For multiple signals, use `--no-wait` and collect results:
```bash
saber signal --domain --question "" --answer-type boolean --no-wait
# Collect signal IDs, then:
saber signal get
```
## Step 5 — Score the lead [#step-5--score-the-lead]
Based on the signal results, produce a qualification score:
**Scoring logic:**
* Count how many signals fired positively
* Weight signals by importance if the user has indicated which matter most
* Factor in any disqualifying signals (e.g. "Is this company a competitor?")
**Output tiers:**
* **Strong fit** — majority of signals positive, no disqualifiers
* **Possible fit** — mixed signals, worth a follow-up conversation
* **Weak fit** — few positive signals or a disqualifier fired
* **Not a fit** — disqualifying signal fired or no positive signals
## Step 6 — Present qualification summary [#step-6--present-qualification-summary]
```
## Qualification: [Company] ([domain])
**Score:** Strong fit / Possible fit / Weak fit / Not a fit
**Signal results:**
| Signal | Result | Notes |
|--------|--------|-------|
| [question] | ✅ Yes / ❌ No | [any context from result] |
**Recommendation:** [1–2 sentences explaining the score and suggested next action]
```
## Step 7 — Suggest next steps [#step-7--suggest-next-steps]
* **Strong fit:** suggest `write-outreach` to draft a personalised first touch
* **Possible fit:** suggest `research-account` to get more context before reaching out
* **Weak/Not a fit:** suggest routing to a nurture sequence or disqualifying
# research-account (/skills/reference/research-account)
**Stage:** Research and qualification
# Research Account [#research-account]
Use this skill to build a comprehensive picture of a target company before reaching out or prioritising them for outreach.
## Goal [#goal]
Produce a research brief covering: company overview, recent news and events, hiring signals, tech stack, LinkedIn presence, and any Saber signal results — ready to inform outreach or qualification decisions.
## Step 1 — Identify the target [#step-1--identify-the-target]
Ask the user for the company name or domain if not already in conversation context.
## Step 2 — Gather available tools [#step-2--gather-available-tools]
Check what research tools are available:
**Saber CLI** (`saber --help`): can run ad-hoc signals against the domain for structured buying intent data.
**MCP tools**: scan available tools for:
* Web search (Brave, Perplexity, Tavily, or similar)
* LinkedIn MCP
* HubSpot MCP (to check if they're already a known contact/account)
* Any other research or enrichment tools
Note what's available — use everything you can find.
## Step 3 — Research the company [#step-3--research-the-company]
Run these in parallel where possible:
### Company overview [#company-overview]
* What do they do, what market, what size, what business model
* Use web search MCP if available, otherwise rely on conversation context or ask the user
### Recent news and events [#recent-news-and-events]
Search for: funding rounds, acquisitions, leadership changes, product launches, layoffs, expansion announcements, press coverage. Focus on the last 6–12 months.
### Hiring signals [#hiring-signals]
Search for open roles on LinkedIn or their careers page. Note:
* Volume of open roles (growth vs. contraction)
* Roles in sales, RevOps, or buyer-relevant departments
* Senior hires that indicate strategic shifts
### Tech stack [#tech-stack]
Check job descriptions, BuiltWith mentions in search results, or G2/Capterra reviews for technology signals — e.g. what CRM, data stack, or infra they run.
### LinkedIn presence [#linkedin-presence]
If a LinkedIn MCP is available, look up the company page:
* Employee count and recent growth
* Recent company posts or announcements
* Key decision-makers and their recent activity
### Saber signal results [#saber-signal-results]
If the Saber CLI is available, check for existing signal data on this domain:
```bash
# Check if signals have already been run for this domain
saber signal --domain --question "Is this company actively hiring in sales or revenue roles?" --answer-type boolean
```
If pre-defined signal subscriptions exist for a list containing this company, retrieve those results:
```bash
saber subscription list
saber subscription get
```
### HubSpot check [#hubspot-check]
If a HubSpot MCP is available, look up whether this company already exists as a contact or deal in HubSpot — note the current stage and any logged activity.
## Step 4 — Present the research brief [#step-4--present-the-research-brief]
Structure the output as a brief:
```
## [Company Name] — Research Brief
**Overview:** 2–3 sentence summary of what they do and why they matter.
**Recent signals:**
- [Key finding from news/events]
- [Key finding from hiring]
- [Saber signal results if available]
**Tech stack:** [Notable tools]
**LinkedIn:** [Employee count, growth trend, notable activity]
**HubSpot status:** [Known / not known; deal stage if applicable]
**Outreach angle:** 1–2 sentence recommendation for how to approach them based on the research.
```
## Step 5 — Suggested next steps [#step-5--suggested-next-steps]
After presenting the brief, suggest:
* Use `write-outreach` to draft a personalised message using this research
* Use `qualify-inbound` if you need a formal signal-based qualification score
* Use `enrich-contacts` to find specific contact details for the people to reach out to
# score-accounts (/skills/reference/score-accounts)
**Stage:** Research and qualification
# Score Accounts [#score-accounts]
Use this skill to rank accounts by the strength of their signals so you can focus outreach
on the highest-intent targets first.
Works with signal data from any source — Saber subscriptions, Apollo exports, HubSpot
properties, or manually researched signals. The scoring algorithm is the same regardless
of where the data comes from.
## Step 1 — Get the signal data [#step-1--get-the-signal-data]
Ask the user how their signal data is available:
***
### Path A — Saber CLI [#path-a--saber-cli]
Run `saber --help` to confirm the CLI is installed.
Find the account list and active subscriptions:
```bash
saber list company list
saber subscription list
```
Fetch results for each relevant subscription:
```bash
saber subscription get
```
If no subscriptions have run yet, offer to activate signals first using `create-company-signals`, then return here.
***
### Path B — Any other source [#path-b--any-other-source]
Ask the user to provide signal results. Accepted formats:
* Paste a table or CSV with columns: Company, Domain, \[Signal 1], \[Signal 2], ...
* Describe what they know for each account (e.g. "Acme raised a round, Beta is hiring SDRs")
* Share a HubSpot export or Apollo enrichment output
Confirm which signals are present and what a positive result looks like for each.
***
## Step 2 — Apply the scoring model [#step-2--apply-the-scoring-model]
### If signal metadata is available (from `generate-signals`) [#if-signal-metadata-is-available-from-generate-signals]
Use the weighted algorithm:
```
1. For each signal, determine if the answer is "positive" per interpretation rules
2. earnedWeight = sum of weights for positive-answer signals
3. totalWeight = sum of all signal weights
4. rawScore = (earnedWeight / totalWeight) × 100
5. Apply penalties:
— Any disqualifier fires wrong → score = 0 immediately
— Zero buying_signal hits → score = 0 (no pain evidence)
— Zero urgency hits → score − 15 (right fit, wrong time)
6. Final score: 0–100
```
**Thresholds:**
* **70+** — High fit, prioritise outreach
* **50–69** — Moderate fit, worth pursuing
* **30–49** — Low fit, monitor for triggers
* **\< 30** — Poor fit, deprioritise
### If no signal metadata is available [#if-no-signal-metadata-is-available]
Ask the user if any signals are more important than others. Then apply a simple model:
* **Base:** +1 per positive signal
* **Priority signals:** 2× weight for signals the user flags as most important
* **Recency bonus:** +0.5 if the signal is from the last 7 days
* Normalise to 0–10 relative to the highest scorer
## Step 3 — Present ranked results [#step-3--present-ranked-results]
```
## Account Scores — [List Name]
### High priority (70+ or top tier)
| Rank | Company | Domain | Score | Top signals |
|------|---------|--------|-------|-------------|
| 1 | Acme Corp | acme.com | 84 | New VP Sales hired, hiring SDRs |
| 2 | Beta Inc | beta.io | 76 | Series B 3 months ago, HubSpot migration |
### Watch list (moderate fit)
| Rank | Company | Domain | Score | Top signal |
|------|---------|--------|-------|------------|
| ... | | | | |
### Deprioritise (low fit)
| Rank | Company | Domain | Score | Notes |
|------|---------|--------|-------|-------|
| ... | | | | |
```
## Step 4 — Suggest next steps [#step-4--suggest-next-steps]
* **High-priority accounts:** use `write-outreach` to draft personalised messages referencing the top signal
* **Watch list:** re-run signals in 2–4 weeks to watch for urgency triggers
* **Low-scoring accounts:** pause or remove from active list; revisit if a trigger fires
* Use `deal-coaching` on any high-priority account that's already in the pipeline
# signal-discovery (/skills/reference/signal-discovery)
**Stage:** Signal foundations
# Signal Discovery [#signal-discovery]
Use this skill as the starting point for signal-based prospecting. It loads your organisation
context, then runs `extract-icp` and `generate-signals` in sequence to produce a complete,
scored signal set ready to activate in Saber.
## Saber CLI check [#saber-cli-check]
Run `saber --help` to check if the Saber CLI is installed.
**If the CLI is available:** proceed with the full workflow below.
**If not installed:** inform the user that this skill works best with the Saber CLI
(available at saber.app), then offer two options:
1. **Install the CLI** — once installed, restart this skill
2. **Continue without the CLI** — skip `saber org get` and ask for org context manually;
the ICP extraction and signal generation steps work fully without the CLI
## Step 1 — Load organisation context [#step-1--load-organisation-context]
**With CLI:** run `saber org get`
* If the profile contains name, website, and description → use them directly, skip asking.
* If the profile is empty or missing key fields → ask the user to fill in the gaps, then persist:
```bash
saber org update --name "..." --general "..." --products "..." --use-cases "..." --value-prop "..."
```
**Without CLI:** ask the user:
* What does your company do?
* Who do you sell to?
* What problem do you solve?
Do not ask questions already answered by available context (e.g. CLAUDE.md).
## Step 2 — Extract the ICP [#step-2--extract-the-icp]
Use the `extract-icp` skill with the company's domain.
This produces a structured ICP covering: target company profile, buying committee,
deal economics, pain points, buying triggers, and existing customers.
Keep the ICP output in conversation context for the next step.
## Step 3 — Generate signals [#step-3--generate-signals]
Use the `generate-signals` skill with the ICP from Step 2.
This produces 12–15 research signals across three categories:
* `icp_fit` — does the company profile match?
* `urgency` — did a recent trigger event happen?
* `buying_signal` — is there evidence of the pain?
Each signal includes answer type, weight (1–3), disqualifier flag, and interpretation rules
needed for automatic scoring.
Confirm the signal set with the user before handing off. Adjust any signals that don't fit.
## Step 4 — Hand off [#step-4--hand-off]
Keep the agreed ICP and signal definitions in conversation context.
Once signals are approved, tell the user their next steps:
* To build a target list: use `build-account-list` or `build-contact-list`
* To activate company signals: use `create-company-signals`
* To activate contact signals: use `create-contact-signals`
* After signals run: use `score-accounts` to rank accounts using the weighted scoring model
# sync-to-hubspot (/skills/reference/sync-to-hubspot)
**Stage:** CRM
# Sync to HubSpot [#sync-to-hubspot]
Use this skill to write Saber signal results back to HubSpot, so sales reps can see intent data directly on contact and company records without leaving their CRM.
## HubSpot MCP check [#hubspot-mcp-check]
Before doing anything else, check if a HubSpot MCP is available by looking for HubSpot-related tools in your available tool list (e.g. tools with `hubspot` in the name for updating contacts, companies, or properties).
**If a HubSpot MCP is available:** proceed with the full workflow below.
**If no HubSpot MCP is detected:** inform the user and offer two options:
1. **Set up the HubSpot MCP** — guide them to install the HubSpot MCP server for Claude Code, which will allow direct CRM writes
2. **Export for manual import** — format the signal results as a CSV that can be imported into HubSpot via the CRM's import tool
## Saber CLI check [#saber-cli-check]
Also check if the Saber CLI is available (`saber --help`). It is needed to fetch signal results.
**If not available:** ask the user to provide signal results directly (paste output or CSV from the Saber dashboard).
## Step 1 — Identify what to sync [#step-1--identify-what-to-sync]
Ask the user:
* Which signal subscription(s) results should be synced?
* Should results be written to **company** records, **contact** records, or both?
Fetch the subscriptions:
```bash
saber subscription list
saber subscription get
```
## Step 2 — Map signals to HubSpot properties [#step-2--map-signals-to-hubspot-properties]
For each signal question, decide how to represent it in HubSpot. Common mappings:
| Signal | HubSpot property type | Example property name |
| ------------------------------------- | ---------------------------- | ------------------------ |
| Boolean signal (yes/no) | Checkbox or single-line text | `saber_hiring_signal` |
| Signal result with detail | Multi-line text | `saber_signal_notes` |
| Signal run date | Date | `saber_last_signal_date` |
| Overall score (from `score-accounts`) | Number | `saber_intent_score` |
Ask the user if these HubSpot properties already exist or need to be created. If they need to be created, use the HubSpot MCP to create them first.
Recommend using a consistent prefix (e.g. `saber_`) for all Saber-sourced properties so they're easy to filter and report on in HubSpot.
## Step 3 — Match Saber companies/contacts to HubSpot records [#step-3--match-saber-companiescontacts-to-hubspot-records]
For each company in the signal results, find the matching HubSpot record:
* Search HubSpot by company domain first (most reliable)
* Fall back to company name if domain doesn't match
For contact records: search by LinkedIn URL or email address.
Note any records that don't have a HubSpot match — present these to the user at the end.
## Step 4 — Write signal results to HubSpot [#step-4--write-signal-results-to-hubspot]
For each matched record, update the mapped properties using the HubSpot MCP:
* Write the signal result (boolean or text)
* Write the date the signal was run
* Write any summary notes from the signal result
Confirm each write succeeded. If any writes fail, note them and continue — don't abort the whole sync.
## Step 5 — Summary [#step-5--summary]
Present a sync summary:
```
## HubSpot Sync Complete
✓ Updated: [N] company records
✓ Updated: [N] contact records
⚠ No HubSpot match found: [list of unmatched companies]
✗ Failed writes: [list of any errors]
Properties written: saber_hiring_signal, saber_funding_signal, saber_last_signal_date
```
Suggest next steps:
* Create a HubSpot list filtered by `saber_intent_score > 7` to build a prioritised outreach view
* Set up a HubSpot workflow triggered by `saber_hiring_signal = true` to auto-enroll high-intent accounts in a sequence
# write-outreach (/skills/reference/write-outreach)
**Stage:** Outreach
# Write Outreach [#write-outreach]
Use this skill to write a personalised cold email or LinkedIn message for a specific contact, using Saber signal results (company-level and contact-level) as the personalisation engine.
## Goal [#goal]
Produce 1–3 outreach variants (cold email, LinkedIn connection request, LinkedIn InMail) that reference specific, timely signals rather than generic opener lines.
## Step 1 — Gather contact context [#step-1--gather-contact-context]
Ask for or confirm from conversation context:
* **Contact name and title**
* **Company name and domain**
* **LinkedIn URL** (if available — needed for contact-level signals)
* **Your name and company** (the sender)
## Step 2 — Pull signal data from Saber [#step-2--pull-signal-data-from-saber]
Signal results are the personalisation fuel. Pull as much as is available.
**If the Saber CLI is available** (`saber --help` works):
*Company-level signals* — check for existing subscription results for this domain:
```bash
saber subscription list
saber subscription get # for any subscription covering this company
```
If no results exist for this company, offer to run a quick spot-check:
```bash
saber signal --domain --question "" --answer-type boolean
```
Suggested spot-check questions if no signals are pre-defined:
* "Is this company actively hiring in sales or revenue roles?"
* "Has this company raised funding in the last 12 months?"
* "Is this company expanding into new markets or geographies?"
*Contact-level signals* — if a LinkedIn URL is available:
```bash
saber signal --profile --question "Is this person posting about [relevant topic]?"
saber signal --profile --question "Has this person recently changed roles or been promoted?"
```
**If the Saber CLI is not available:** ask the user to share any signal results they have, or describe what they know about the contact and company. Proceed with whatever context is available.
## Step 3 — Identify the best personalisation angle [#step-3--identify-the-best-personalisation-angle]
From the signal results and context, pick the strongest hook — the one most likely to resonate with this specific contact. Prioritise:
1. **Recent, specific events** (funding, launch, hiring surge, leadership change) — shows you did your homework
2. **Role-specific pain** (their title implies a problem; signals confirm it) — shows relevance
3. **Contact's own words** (if contact-level signals show recent posts or activity) — most personal
Avoid generic openers like "I noticed you're growing fast" unless signals specifically confirm growth.
## Step 4 — Confirm value prop and ask [#step-4--confirm-value-prop-and-ask]
Before writing, confirm:
* **What outcome does the sender want from this message?** (book a call, get a reply, share a resource)
* **What's the core value prop in one sentence?**
If this is already clear from conversation context (e.g. previous `signal-discovery` run), use it directly.
## Step 5 — Write the outreach [#step-5--write-the-outreach]
Write all three variants using the chosen personalisation angle:
### Cold email [#cold-email]
* **Subject line:** specific, not clever — reference the signal hook
* **Opening line:** the personalisation hook (1 sentence, no fluff)
* **Value prop:** what you do and why it's relevant to them right now (2–3 sentences)
* **CTA:** one clear, low-friction ask (reply, 15-min call, a specific question)
* **Length:** under 120 words in the body
### LinkedIn connection request (300 chars max) [#linkedin-connection-request-300-chars-max]
* Lead with the personalisation hook
* One-line value prop
* No hard CTA — the connection itself is the ask
### LinkedIn InMail [#linkedin-inmail]
* Similar structure to the cold email but slightly warmer tone
* Can be slightly longer (150 words) given the LinkedIn context
## Step 6 — Present variants and offer to adjust [#step-6--present-variants-and-offer-to-adjust]
Show all three variants clearly labelled. Ask:
* Which angle feels most accurate to the contact's situation?
* Does the value prop feel right?
* Offer to write 2–3 subject line alternatives for the email
## Notes on quality [#notes-on-quality]
* Never fabricate signal data — only reference what was actually returned
* If a signal came back negative or ambiguous, don't use it as a hook
* The best outreach sounds like it was written by someone who actually read the signals, not generated by AI
# Workflows (/skills/workflows)
## ICP to prioritised pipeline [#icp-to-prioritised-pipeline]
1. Run `signal-discovery` to extract ICP context and generate candidate signals.
2. Run `build-account-list` to produce a target account list.
3. Run `score-accounts` to rank the list by signal strength.
4. Run `write-outreach` for high-priority accounts.
## Account research for a meeting [#account-research-for-a-meeting]
1. Run `research-account` with the company domain.
2. Ask for a call prep summary, buying triggers, and recent changes.
3. Run `write-outreach` or `deal-coaching` using the account brief.
## Monitor and react [#monitor-and-react]
1. Run `create-company-signals` to define recurring signal tracking.
2. Run `manage-signals` to review active subscriptions.
3. Use `sync-to-hubspot` to write results into CRM properties.
## Expansion motion [#expansion-motion]
1. Run `find-expansion-accounts` against customer data.
2. Score expansion candidates with `score-accounts`.
3. Use `build-sequence` to create account-specific upsell messaging.
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}
{
"openapi": "3.2.0",
"info": {
"title": "Saber Platform API",
"description": "The Saber Platform API provides AI-powered research signals for companies and contacts.\n\n## Quick Start\n\nGet your first AI-powered company insights in just 2 steps. No coding experience required!\n\n> **For AI Models & Agents:** If you are an AI model, agent, or automated system integrating with this API, fetch the OpenAPI specification directly at `GET https://api.saber.app/v1/openapi.yaml` for structured, machine-readable documentation with complete endpoint definitions, request/response schemas, parameter enums, and examples.\n\n---\n\n### Step 1: Get Your API Key\n\nThink of your API key as a password that lets you access Saber's AI research capabilities.\n\n**Here's how to get it:**\n\n1. **Log in** to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. **Click on Settings** (in the bottom-left corner)\n3. **Find the \"API Keys\" section** in the settings menu\n4. **Click \"Create API Key\"** and give it a memorable name (like \"My First Key\")\n5. **Copy the key immediately** - You'll see something like `sk_live_abc123xyz...`\n\n ⚠️ **Important:** This key will only be shown once! Save it somewhere safe.\n\n**Where to store your API key:**\n- On your computer: Save it in a text file or password manager\n- In your code: Use an environment variable (your developer will know what this means)\n- Don't share it publicly or commit it to GitHub!\n\nYour API key looks like this: `sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`\n\n---\n\n### Step 2: Make Your First Request\n\nNow let's ask Saber to research a company! We'll use Clay.com as an example.\n\n**Copy this command and paste it into your terminal** (replace `YOUR_API_KEY` with the key you just copied):\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**What this command does:**\n- `curl` - A tool that sends web requests (it's already on your computer!)\n- `https://api.saber.app/v1/companies/signals/sync` - The Saber API address\n- `Authorization: Bearer YOUR_API_KEY` - Your secret key that proves it's you\n- `\"domain\": \"clay.com\"` - The company website you want to research\n- `\"question\": \"Does this company have open roles in operations?\"` - What you want to know\n- `\"answerType\": \"boolean\"` - You want a yes/no answer\n\n**How to run this:**\n1. **Windows:** Open \"Command Prompt\" or \"PowerShell\"\n2. **Mac:** Open \"Terminal\" (find it in Applications → Utilities)\n3. **Linux:** Open your terminal application\n4. Paste the command and press Enter\n5. Wait 30-60 seconds while Saber's AI researches the company\n\n---\n\n### What You'll Get Back\n\nSaber will return a JSON response (a structured data format) that looks like this:\n\n```json\n{\n \"id\": \"e45c1dc4-d422-4b51-956b-cb6d3ddaf250\",\n \"status\": \"completed\",\n \"domain\": \"clay.com\",\n \"question\": \"Does this company have open roles in operations?\",\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"completedAt\": \"2024-01-15T10:32:15Z\",\n \"answer\": {\n \"type\": \"boolean\",\n \"boolean\": {\n \"value\": true\n }\n },\n \"reasoning\": \"Based on their careers page, Clay has multiple open positions in Operations\",\n \"confidence\": 0.95,\n \"sources\": [\n {\n \"url\": \"https://clay.com/careers\",\n \"title\": \"Careers at Clay\",\n \"snippet\": \"We're hiring for Operations Manager and Operations Coordinator roles...\"\n }\n ]\n}\n```\n\n**Understanding the response:**\n- **`status: \"completed\"`** - Your research is done!\n- **`answer.boolean.value: true`** - Yes, they have open operations roles\n- **`reasoning`** - Why Saber thinks this is the answer\n- **`confidence: 0.95`** - Saber is 95% confident (0 = no confidence, 1 = totally sure)\n- **`sources`** - Where Saber found this information (with direct links!)\n\n---\n\n### Try Different Questions\n\nChange the `\"question\"` in your command to ask different things about any company:\n\n**Yes/No Questions** (use `\"answerType\": \"boolean\"`):\n- \"Does this company have open roles in operations?\"\n- \"Does this company use HubSpot?\"\n- \"Are they venture-backed?\"\n- \"Do they have a remote work policy?\"\n\n**Numeric Questions** (use `\"answerType\": \"number\"`):\n- \"What is the size of their DevOps team?\"\n- \"How many employees work here?\"\n- \"What year was the company founded?\"\n\n**Open-Ended Questions** (use `\"answerType\": \"open_text\"`):\n- \"What industry does this company serve?\"\n- \"What is their main product offering?\"\n- \"Who are their main competitors?\"\n\n**URL Questions** (use `\"answerType\": \"url\"`):\n- \"What is the URL to their careers page?\"\n- \"What is the link to their pricing page?\"\n- \"What is their LinkedIn company page URL?\"\n\n**Pro tip:** Change `\"domain\": \"clay.com\"` to any company website (like `\"stripe.com\"`, `\"shopify.com\"`, etc.)\n\n---\n\n### What Happens Behind the Scenes\n\nWhen you make a request:\n\n1. 🚀 **Saber receives your question** about the company\n2. 🤖 **AI analyzes** the company's website, career pages, social media, and public data\n3. 🧠 **AI finds the answer** and determines how confident it is\n4. 📚 **Collects sources** so you can verify the information\n5. ✅ **Returns everything** in one response (usually 30-60 seconds)\n\n---\n\n### Need Help?\n\n**Common issues:**\n\n- **\"Unauthorized\" error?** → Check that you copied your API key correctly (including `sk_live_`)\n- **\"curl command not found\"?** → curl is pre-installed on Mac/Linux. Windows users: use PowerShell or install [Git Bash](https://git-scm.com/downloads)\n- **Request taking too long?** → Some questions need more research time. You can set a custom timeout (see advanced docs below)\n- **Getting unexpected answers?** → Try rephrasing your question to be more specific\n\n---\n\n### More Examples to Try\n\nNow that you've made your first request, try these other common use cases:\n\n**Example 1: Check if a company uses specific technology**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"Does this company use AWS for their infrastructure?\",\n \"answerType\": \"boolean\"\n }'\n```\n\n**Example 2: Get a company's employee count**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"shopify.com\",\n \"question\": \"How many employees work at this company?\",\n \"answerType\": \"number\"\n }'\n```\n\n**Example 3: Learn about a company's business model**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"notion.so\",\n \"question\": \"What is their primary revenue model?\",\n \"answerType\": \"open_text\"\n }'\n```\n\n**Example 4: Get a specific URL from a company**\n\n```bash\ncurl -X POST https://api.saber.app/v1/companies/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"domain\": \"stripe.com\",\n \"question\": \"What is the URL to their careers page?\",\n \"answerType\": \"url\"\n }'\n```\n\n---\n\n**That's it!** You now know how to use Saber's API to get AI-powered insights about any company. Simply change the `domain` and `question` fields to research different companies and ask different questions.\n\n---\n\n**📌 Note:** Some features like contact search and contact signals require the Saber Chrome Extension for LinkedIn integration. See the [LinkedIn Integration Setup](#setting-up-linkedin-integration) section below for installation instructions.\n\n---\n\n## Authentication\n\nAll API requests require authentication using an API key. Include your API key in the Authorization header:\n\n```\nAuthorization: Bearer sk_live_your_api_key_here\n```\n\n### Getting Your API Key\n\n1. Log in to your Saber account at [ai.saber.app](https://ai.saber.app)\n2. Navigate to **Settings** → **API Keys**\n3. Click **Create API Key** and provide a name\n4. Copy the key immediately - it will only be shown once\n5. Store the key securely (environment variable, secret manager, or vault)\n\n### Security Best Practices\n\n**Do:**\n- Store keys securely using environment variables or secret managers\n- Use different API keys for different environments (staging, production)\n- Monitor API usage logs regularly\n- Rotate keys periodically\n\n**Don't:**\n- Hardcode API keys in your source code\n- Share API keys in public repositories or documentation\n- Use the same API key across multiple external systems\n- Commit API keys to version control\n\n---\n\n## Setting Up LinkedIn Integration\n\nSome of Saber's most powerful features leverage your LinkedIn and Sales Navigator access to provide contact-level research and discovery. The Saber Chrome Extension enables these capabilities by securely connecting your professional network to the API.\n\n**⚡ Pro tip:** The more users in your organization who install and connect the extension, the better your API's performance and data coverage will be.\n\n---\n\n### What This Enables\n\nInstalling and connecting the Chrome Extension unlocks:\n\n- **Contact Search** - Find contacts at companies using Sales Navigator's filters (job titles, keywords, locations)\n- **Contact Signals** - Generate AI-powered insights about individual contacts from their LinkedIn profiles\n- **Enhanced Contact Research** - Enrich contact records with verified professional information\n- **Deeper Company Data** - Access more detailed company information through LinkedIn's network\n\nWithout the extension, these endpoints will return errors directing you to install it.\n\n---\n\n### Prerequisites\n\nBefore installing the Chrome Extension:\n\n1. **Active LinkedIn account** - A standard LinkedIn account\n2. **Sales Navigator subscription** (recommended) - Required for contact search features. Basic LinkedIn access enables contact signals but with limited data.\n3. **Saber account** - Log in at [ai.saber.app](https://ai.saber.app)\n4. **Chrome browser** - Currently supports Chrome/Chromium-based browsers\n\n---\n\n### Installation Steps\n\n1. **Install the extension** from the Chrome Web Store:\n\n [Saber Chrome Extension](https://chromewebstore.google.com/detail/saber-sales-signals-and-i/hekbghpjfchhcbgmebbnohennnjlhmmp)\n\n2. **Click \"Add to Chrome\"** and confirm installation\n\n3. **Pin the extension** (optional but recommended):\n - Click the puzzle icon in Chrome's toolbar\n - Find \"Saber\" and click the pin icon\n\n4. **Log in to Saber** in the extension:\n - Click the Saber extension icon\n - Sign in with your Saber credentials\n\n---\n\n### Connecting Your LinkedIn Account\n\nAfter installing the extension:\n\n1. **Visit LinkedIn** - Navigate to [linkedin.com](https://linkedin.com) and ensure you're logged in\n\n2. **Open the Saber extension** while on LinkedIn\n\n3. **Click \"Connect LinkedIn Account\"** - The extension will detect your LinkedIn session\n\n4. **Verify connection** - You should see a success message and \"Connected\" status\n\n**For Sales Navigator access:**\n1. Visit [linkedin.com/sales](https://linkedin.com/sales) and verify you can access Sales Navigator\n2. Return to the Saber extension and refresh the connection\n3. The extension will automatically detect your Sales Navigator subscription\n\nYour LinkedIn credentials are used only to authenticate API requests and are never stored by Saber. The extension operates as a secure bridge between LinkedIn and the Saber API.\n\n**🏢 Team usage:** We recommend having multiple team members install and connect the extension. This improves reliability, data freshness, and API performance for your entire organization.\n\n---\n\n### What You Can Do Now\n\nWith LinkedIn integration active, you can use these API endpoints:\n\n**Contact Search** - `/v1/contacts/search`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/search \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"companyLinkedInUrls\": [\"https://linkedin.com/company/stripe\"],\n \"jobTitles\": [\"VP of Sales\", \"Head of Sales\"],\n \"countries\": [\"US\"]\n }'\n```\n\n**Contact Signals** - `/v1/contacts/signals/sync`\n```bash\ncurl -X POST https://api.saber.app/v1/contacts/signals/sync \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"contactProfileUrl\": \"https://linkedin.com/in/patrickcollison\",\n \"question\": \"Is this person actively posting about payments or fintech?\",\n \"answerType\": \"boolean\"\n }'\n```\n\nSee the full endpoint documentation below for all available parameters and options.\n\n---\n\n### Troubleshooting\n\n**\"LinkedIn Sales Navigator connector not found\" error?**\n- Ensure you've connected your LinkedIn account through the extension\n- Visit [linkedin.com](https://linkedin.com) and verify you're logged in\n- Open the Saber extension and check connection status\n- Try disconnecting and reconnecting your account\n\n**\"Sales Navigator access required\" error?**\n- This feature requires an active Sales Navigator subscription\n- Verify you can access [linkedin.com/sales](https://linkedin.com/sales)\n- Refresh your connection in the Saber extension\n- Some contact features work with basic LinkedIn (check endpoint documentation)\n\n**\"Connection expired or is invalid\" error?**\n- LinkedIn sessions expire periodically\n- Reconnect through the extension (it takes just a few seconds)\n- Ensure you're logged into LinkedIn in the same browser\n\n**Extension not detecting LinkedIn?**\n- Make sure you're on linkedin.com (not a subpage)\n- Try refreshing the page\n- Check that the extension is enabled in Chrome settings\n- Reinstall the extension if issues persist\n\n**Need help?** Contact support at [ai.saber.app](https://ai.saber.app).\n\n---\n",
"version": "1.0.0",
"contact": {
"name": "Saber Platform Support",
"url": "https://ai.saber.app"
},
"license": {
"name": "Proprietary",
"url": "https://saber.app/terms"
}
},
"servers": [
{
"url": "https://api.saber.app",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"paths": {
"/v1/contacts/find-email": {
"post": {
"summary": "Find a verified email address for a contact at a domain",
"description": "Given a contact's full name and the domain of the company they\nwork for, returns a verified email address along with verification\nmetadata.\n\nUnder the hood, the service maintains a learning pattern store: it\ntries patterns previously verified for the domain first (the\n\"warm path\"), then falls back to a parallel sweep of all common\ntemplates against the Emailable verifier (the \"cold path\") if no\ncached pattern verifies. Patterns are global — every customer's\nverification feeds every other customer's first lookup at that\ndomain.\n\nNames with European tussenvoegsels (`Joey van Ommen`,\n`Joey van der Ommen`) are handled — the service tries both\n\"tussenvoegsel-included\" and \"tussenvoegsel-excluded\" surname\nrenderings.\n\n### Response semantics\n\nAlways branch on the `status` field first — do not null-check\n`verification` directly.\n\n- `status: found` → `email + verification` are present; a\n deliverable or risky mailbox was found at this domain.\n- `status: not_found` → `email` and `verification` are explicit\n `null`; we tried the full candidate set within a 15-second\n budget and no mailbox verified as deliverable. Distinct from\n a server error. A non-person input (a company name in the name\n field) also settles here rather than as a 422.\n- **Non-Latin names** (Arabic, Chinese, etc.) are transliterated\n to ASCII and attempted on a best-effort basis — they are no\n longer auto-`not_found`. Because transliteration is\n approximate (Arabic omits short vowels), such names — and\n heavily-recovered long compound names — are treated as\n **low-confidence**: they return `found` only on a confirmed\n deliverable mailbox, and never return a catch-all \"best guess\"\n (they settle as `not_found`/`inconclusive` instead).\n- `status: inconclusive` → `email` and `verification` are\n `null`, but this is **not** a definitive miss: the mail server\n greylisted us (a temporary \"try again later\" deferral) or the\n verification budget ran out before we could confirm. The\n contact is likely reachable — retry the same request later\n rather than discarding it. Previously these cases surfaced as a\n `408` timeout error; they now return a stable `200` so an\n identical request no longer flips between error and not-found.\n- `verification.accept_all = true` → the domain is configured\n as catch-all, so the verifier cannot distinguish real mailboxes\n from accepted-but-discarded ones. The returned email is our\n best guess (modal real-world pattern) but should be treated as\n lower confidence. This catch-all best guess is only returned for\n high-confidence parses; a low-confidence name (transliterated or\n heavily-recovered compound) settles as `not_found` instead.\n\n## Credits\n\nEach call charges **0.1 credits** when it produces an answer —\nboth when a deliverable email is found and when the definitive\nanswer is `not_found` after real verification work. A `found`\nresult always charges, including when it is served from Saber's\nverification cache: you pay per answer delivered, not per\nverification run. You are NOT charged when:\n\n- the result is `inconclusive` — we couldn't determine an answer,\n and the advised retry of the same request would otherwise pay\n twice for one answer;\n- no verification work was needed and no email was found:\n validation errors, upstream outages, pre-filter short-circuits\n (e.g. invalid domain syntax), and `not_found` repeats served\n entirely from Saber's verification cache;\n- the only verification work ran on a domain Saber discovered\n speculatively (dead-domain successor search) rather than the\n domain you supplied.\n\nRequests rejected due to insufficient credit balance return `402`\nbefore any verification work is performed.\n",
"operationId": "findEmail",
"x-mcp-hidden": true,
"deprecated": true,
"x-credit-feature": "api-post-contacts-find-email",
"x-credit-cost": 0.1,
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
},
"tussenvoegsel": {
"summary": "Dutch tussenvoegsel name",
"value": {
"full_name": "Joey van Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The request was processed within the budget. The response\nmay carry a found email or explicit nulls for the\nnot-found case — both are 200 because the request itself\nsucceeded.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
},
"examples": {
"found": {
"summary": "Deliverable mailbox found",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 95,
"accept_all": false
}
}
},
"not_found": {
"summary": "No deliverable mailbox found within budget",
"value": {
"status": "not_found",
"email": null,
"verification": null
}
},
"inconclusive": {
"summary": "Reachable but unconfirmed — retry later",
"value": {
"status": "inconclusive",
"email": null,
"verification": null
}
},
"catch_all": {
"summary": "Catch-all domain — best guess returned",
"value": {
"status": "found",
"email": "joey.vanommen@saber.app",
"verification": {
"state": "deliverable",
"score": 90,
"accept_all": true
}
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits - the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"type": "PAYMENT_REQUIRED",
"code": "PAYMENT_REQUIRED",
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Organization has insufficient credits",
"details": {
"remainingCredits": 0.05
},
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
}
},
"422": {
"description": "Unparseable input. The name parser first strips professional\ncredentials and honorifics (`MBA`, `MRICS`, `Dr.`, …),\ndrops parenthetical/role noise, and prefers the Latin part of\na mixed-script name, so many previously-rejected names now\nresolve. It rejects only a genuinely unparseable name — a\nsingle lone token, or empty — or a domain that is URL-shaped\n/ email-shaped / syntactically invalid. Note: a value that is\nactually a **company name** (`Best Vision Business Center`) or\na non-Latin string with no usable name token is NOT a 422 —\nthose settle as a clean `200 not_found`.\n"
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
"examples": {
"upstream_throttle": {
"summary": "Emailable upstream throttled us — Retry-After set",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "upstream email verification is temporarily throttled",
"retryAfter": 30
}
}
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/contacts/find-email/batch": {
"post": {
"summary": "Submit a bulk find-email batch",
"description": "Accepts up to 1,000 `(full_name, domain)` pairs and resolves\nthem asynchronously through the same engine as the single\n`find-email` endpoint. Returns `202` with a `batch_id`\nimmediately; poll `GET /v1/contacts/find-email/batch/{id}` for\nprogress and per-contact results.\n\nServer-side behaviors that replace client-side loops:\n\n- **Pacing + retries against the upstream verifier** — no\n client-side 429 handling needed; throttle waves are absorbed\n with backoff inside the batch.\n- **Domain grouping** — contacts at the same domain are resolved\n together, so the first resolution warms the pattern store and\n the rest reuse it.\n- **Domain normalization + duplicate collapsing** — domains are\n normalized (lowercased, `www.` and trailing dots stripped)\n before grouping, and case-insensitive duplicate\n `(full_name, domain)` pairs are collapsed; `row_count` is the\n deduplicated count. Poll results echo the **normalized**\n domain.\n- **Partial results, always** — an upstream failure affecting\n one domain never discards other rows' results; affected rows\n end in `status: error` with an `error_code`.\n",
"operationId": "findEmailBatchSubmit",
"x-mcp-name": "contacts-enrich_work_email_batch",
"tags": [
"Email Finder"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"contacts"
],
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"items": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Same semantics as the single find-email endpoint."
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "Same semantics as the single find-email endpoint."
}
}
}
}
}
},
"examples": {
"basic": {
"summary": "Two contacts, one shared domain",
"value": {
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com"
},
{
"full_name": "Kathy Kaplan",
"domain": "collectiveartists.com"
}
]
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; processing started.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"row_count"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending"
]
},
"row_count": {
"type": "integer",
"description": "Number of contacts after duplicate collapsing."
}
}
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON)."
},
"401": {
"description": "Missing or invalid authentication."
},
"422": {
"description": "Empty list, more than 1,000 entries, or structurally invalid\nrows. The error message names the offending zero-based\nindices so the caller can fix the exact rows and resubmit.\n"
},
"429": {
"description": "Per-API-key rate limit exceeded."
}
}
}
},
"/v1/contacts/find-email/batch/{id}": {
"get": {
"summary": "Poll a bulk find-email batch",
"description": "Returns the batch status, progress counters, and every\ncontact's current outcome. Rows resolve incrementally — poll\nuntil `status` is `completed` (or, rarely, `failed`).\nPer-contact `status` values:\n\n- `pending` — not resolved yet.\n- `found` — `email` + `verification` present (same shape as the\n single endpoint).\n- `not_found` — resolved definitively with no verified mailbox;\n `email` and `verification` are explicit `null`.\n- `inconclusive` — reachable but unconfirmed (greylisting /\n verification budget exhausted); `email` and `verification` are\n `null`. Same meaning as the single endpoint — not a definitive\n miss, retry the contact later. Distinct from `not_found`.\n- `error` — terminal failure for this row; `error_code` is one\n of `invalid_input` (name/domain unparseable),\n `upstream_failed` (verifier failure on this domain), or\n `incomplete` (the batch finished but this row's domain group\n exhausted its retries).\n\nBatches are retained for 30 days after creation.\n",
"operationId": "findEmailBatchGet",
"x-mcp-name": "contacts-enrich_work_email_batch_get",
"tags": [
"Email Finder"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "The `batch_id` returned by the submit call."
}
],
"responses": {
"200": {
"description": "Current batch state.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"batch_id",
"status",
"progress",
"contacts"
],
"properties": {
"batch_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"pending",
"running",
"completed",
"failed"
],
"description": "`failed` is rare: the batch's own bookkeeping hit\nan unrecoverable infrastructure failure. Rows that\nresolved before the failure keep their results;\nthe rest are `error/incomplete`. Row-level\nresolution failures never produce `failed` — they\nsurface as per-row `error` on a `completed` batch.\n"
},
"progress": {
"type": "object",
"required": [
"total",
"pending",
"found",
"not_found",
"inconclusive",
"error"
],
"properties": {
"total": {
"type": "integer"
},
"pending": {
"type": "integer"
},
"found": {
"type": "integer"
},
"not_found": {
"type": "integer"
},
"inconclusive": {
"type": "integer"
},
"error": {
"type": "integer"
}
}
},
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"full_name",
"domain",
"status",
"email",
"verification"
],
"properties": {
"full_name": {
"type": "string"
},
"domain": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"pending",
"found",
"not_found",
"inconclusive",
"error"
]
},
"email": {
"type": [
"string",
"null"
]
},
"verification": {
"type": [
"object",
"null"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
]
},
"score": {
"type": "integer"
},
"accept_all": {
"type": "boolean"
}
}
},
"error_code": {
"type": "string",
"enum": [
"invalid_input",
"upstream_failed",
"incomplete"
]
}
}
}
}
}
},
"examples": {
"running": {
"summary": "Mid-flight batch with partial results",
"value": {
"batch_id": "6f1a1c9e-9df9-4a4f-9df0-2f8f6f7a4b31",
"status": "running",
"progress": {
"total": 3,
"pending": 1,
"found": 1,
"not_found": 1,
"inconclusive": 0,
"error": 0
},
"contacts": [
{
"full_name": "Aadil Merchant",
"domain": "collectiveartists.com",
"status": "found",
"email": "aadil.merchant@collectiveartists.com",
"verification": {
"state": "deliverable",
"score": 93,
"accept_all": false
}
},
{
"full_name": "Manjula K.L",
"domain": "capstonelife.in",
"status": "not_found",
"email": null,
"verification": null
},
{
"full_name": "Kathy Kaplan",
"domain": "relevatehealth.com",
"status": "pending",
"email": null,
"verification": null
}
]
}
}
}
}
}
},
"401": {
"description": "Missing or invalid authentication."
},
"404": {
"description": "Unknown batch id, or the batch belongs to another organization."
},
"422": {
"description": "The id path parameter is not a UUID."
}
}
}
},
"/v1/signals/sync/funding": {
"post": {
"summary": "Funding (sync)",
"description": "Synchronous form of the Funding signal (`POST /v1/signals/funding`):\ncreates the signal and blocks until it completes (or the timeout is\nreached), returning the result in the same response. Recommended for\ninteractive and agent workflows. If you supply a `webhookUrl`, the completed signal is\nstill delivered to it in addition to this response — omit it unless\nyou want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"completed": {
"summary": "Completed funding signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/funding": {
"post": {
"summary": "Funding (async)",
"description": "Summarise a company's most recent funding and public/IPO status from a\ndomain.\n\nThe AI confirms sources belong to the company at that domain, reports the\nlatest **primary** equity round (never a secondary, tender, or debt\nevent), and flags a completed IPO / direct listing / SPAC as\n`isPublic: true` with `latestRound: \"IPO\"`.\n\nThis is the **async** endpoint: it returns `201` immediately with a\n`processing` signal. Poll `GET /v1/companies/signals/{signalId}` or supply a\n`webhookUrl` to receive the completed result. For a single blocking\nrequest, use `POST /v1/signals/sync/funding`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FundingSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFundingSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-funding",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's most recent funding round and public/IPO status by domain. Returns a structured funding summary with investors and source URLs.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
},
"with_webhook": {
"summary": "Deliver the completed result to a webhook",
"value": {
"domain": "stripe.com",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure and its `retryable` flag ride in the body, not the HTTP status).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "failed",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FundingSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "processing",
"domain": "stripe.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"signalType": "COMPANY",
"namedSignal": "funding",
"status": "completed",
"domain": "stripe.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:40Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"hasFundingEvent": true,
"isPublic": false,
"latestRound": "Series I",
"latestEvents": [
{
"eventType": "funding_round",
"round": "Series I",
"amount": 6500000000,
"currency": "USD",
"investors": [
"Andreessen Horowitz",
"Sequoia Capital",
"General Catalyst"
],
"announcedAt": "2023-03-15",
"sourceUrls": [
"https://stripe.com/newsroom/news/series-i"
]
}
]
}
},
"confidence": 0.93,
"sources": [
{
"url": "https://stripe.com/newsroom/news/series-i",
"title": "Stripe raises Series I"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/mna": {
"post": {
"summary": "M&A (sync)",
"description": "Synchronous form of the M&A signal (`POST /v1/signals/mna`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"completed": {
"summary": "Completed M&A signal — terminated deal",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/mna": {
"post": {
"summary": "M&A (async)",
"description": "Detect whether a company was acquired, or made acquisitions, in the\nrecent window — counterparty, deal status, and value where disclosed.\n\nCounts only true acquisitions (control transfer): minority investments,\nfunding rounds, and partnerships are excluded. `role` distinguishes the\nqueried company being bought (`acquiree`, reported at any age) from it\nbuying others (`acquirer`, last 24 months only). An announced deal that\nlater collapsed is `terminated`, never `completed`. An acquisition backed\n**only** by a data aggregator with no primary corroboration stays\n`rumored` and does not count as `detected`.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/mna`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`MAndASignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createMnaSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-mna",
"x-mcp-hidden": true,
"x-mcp-description": "Research a company's recent M&A activity by domain — whether it was acquired or made acquisitions, with counterparty, deal status, and value.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A for a single company",
"value": {
"domain": "figma.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "failed",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MAndASignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "processing",
"domain": "figma.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"signalType": "COMPANY",
"namedSignal": "mna",
"status": "completed",
"domain": "figma.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:20Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"detected": true,
"latestEvents": [
{
"role": "acquiree",
"counterparty": "Adobe",
"status": "terminated",
"amount": 20000000000,
"currency": "USD",
"announcedAt": "2022-09-15",
"sourceUrls": [
"https://news.adobe.com/news/2023/12/adobe-figma-update"
]
}
]
}
},
"confidence": 0.97,
"sources": [
{
"url": "https://news.adobe.com/news/2023/12/adobe-figma-update",
"title": "Adobe and Figma mutually terminate merger agreement"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/tech": {
"post": {
"summary": "Tech (sync)",
"description": "Synchronous form of the Tech signal (`POST /v1/signals/tech`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. Supply exactly one of `category` or `technology`.\nIf you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"category_completed": {
"summary": "Completed CRM category signal",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/tech": {
"post": {
"summary": "Tech (async)",
"description": "Detect which system(s) a company currently uses in a technology\ncategory, verified with public web evidence.\n\nSupply **exactly one** typed parameter:\n- `category` — a coarse category (`erp` or `crm`). Returns every system\n detected in that category, with the current system of record in\n `primary`.\n- `technology` — a specific product (free text resolved to a canonical\n slug, e.g. `salesforce`, `SAP S/4HANA`). Returns a one-element\n `technologies` list whose `status` carries the verdict (`active`,\n `churned`, or `not_found`).\n\n**Vendor data is a prior, not the answer.** Technographics detections\nenter the agent loop only as leads to verify; every reported technology\nmust cite public web evidence — a job posting, engineering blog, docs, a\ncase study, or a live website fingerprint. A vendor-only hint that can't\nbe corroborated surfaces as `status: not_found`, never as a confirmed\nfinding.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/tech`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`TechSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createTechSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-tech",
"x-mcp-hidden": true,
"x-mcp-description": "Detect the ERP or CRM system(s) a company uses, or verify a specific technology, by domain — verified with public web evidence, not vendor data alone.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category_crm": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "monday.com",
"category": "crm"
}
},
"category_erp": {
"summary": "Which ERP does this company use?",
"value": {
"domain": "monday.com",
"category": "erp"
}
},
"specific_technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "monday.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "failed",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "processing",
"domain": "monday.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffb0472",
"signalType": "COMPANY",
"namedSignal": "tech",
"status": "completed",
"domain": "monday.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:32:05Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"scope": "category",
"category": "crm",
"primary": "salesforce",
"technologies": [
{
"technology": "salesforce",
"displayName": "Salesforce",
"status": "active",
"confidence": 0.9,
"lastEvidence": "2026-05",
"evidence": [
{
"type": "job_posting",
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator"
}
]
}
]
}
},
"confidence": 0.9,
"sources": [
{
"url": "https://monday.com/careers/salesforce-admin",
"title": "Salesforce Administrator — Careers"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid: neither or both of `category`/`technology`\nsupplied, an unknown `category`, or an unresolvable `technology`\n(returned with did-you-mean suggestions).\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/open-jobs": {
"post": {
"summary": "Open Jobs (sync)",
"description": "Synchronous form of the Open Jobs signal (`POST /v1/signals/open-jobs`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed open-jobs signal",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/open-jobs": {
"post": {
"summary": "Open Jobs (async)",
"description": "Summarise what a company is hiring for from public job postings: how many\nroles are open, the function areas and technologies behind them, and\nwhere.\n\n`openings` is the **true total** matching the trailing 90-day window (the\njob search tool's reported match count), not the number of sampled\nlistings. `topTitles`, `hiringThemes`, `techHiringFor`, and `locations`\nare drawn from a small sample and are **examples**, not a proportional\nbreakdown. `hiringThemes` maps roles onto a fixed function taxonomy so the\nfield stays filterable and stable.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/open-jobs`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`OpenJobsSignalAnswer`. **Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createOpenJobsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-open_jobs",
"x-mcp-hidden": true,
"x-mcp-description": "Summarise a company's current hiring from public job postings by domain — total openings, example titles, function areas, technologies, and locations.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "datadoghq.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "failed",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OpenJobsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "processing",
"domain": "datadoghq.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "b78e4bd7-b755-6e84-c89e-fe9f6bbc0583",
"signalType": "COMPANY",
"namedSignal": "open-jobs",
"status": "completed",
"domain": "datadoghq.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:10Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"openings": 876,
"topTitles": [
"Senior Software Engineer",
"Product Manager",
"Enterprise Account Executive"
],
"hiringThemes": [
"Engineering",
"Product",
"Sales/GTM"
],
"techHiringFor": [
"Go",
"Kubernetes",
"React"
],
"locations": [
"New York, NY",
"Remote",
"Paris, France"
],
"latestPostingDate": "2026-06-28",
"source": "public_job_listings"
}
},
"confidence": 0.9
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/sync/firmographics": {
"post": {
"summary": "Firmographics (sync)",
"description": "Synchronous form of the Firmographics signal (`POST /v1/signals/firmographics`):\ncreates the signal and blocks until it completes, returning the result in\nthe same response. If you supply a `webhookUrl`, the completed signal is still delivered to it in addition to this response — omit it unless you want both.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"$ref": "#/components/parameters/SyncTimeoutSec"
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, terminally failed (the failure and its `retryable` flag ride in the body), or — if the timeout elapsed — still `processing`. Check the body's `status`, not the HTTP code; poll `GET /v1/companies/signals/{signalId}` for a `processing` result.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"completed": {
"summary": "Completed firmographics signal",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/signals/firmographics": {
"post": {
"summary": "Firmographics (async)",
"description": "Return a company's base profile — headcount, size band, entity type,\nfounded year, HQ location — plus a **Saber-derived category** that refines\nthe coarse public industry tag, with cited web evidence.\n\nThe differentiator is `saberCategory`: a generalized→niche classification\nthat corrects the over-broad public industry label (which tags many\nfirms simply as \"Software\") using the company's own site and product\npages. `segments` captures who the company sells to (`B2B`, `B2C`,\n`B2B2C`, `B2G`, `C2C`). Authoritative enrichment priors (headcount,\nLinkedIn followers, type, founded year, website) are copied verbatim when\nknown and researched only when absent — never guessed.\n\nThis is the **async** endpoint. For a single blocking request, use\n`POST /v1/signals/sync/firmographics`.\n\nThe completed answer (`answer.jsonSchema`) conforms to\n`FirmographicsSignalAnswer`.\n**Cost:** 2 credits, charged once per result delivered to your organization — your own repeat calls served from cache are free, and failed runs are never charged.\n",
"operationId": "createFirmographicsSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Prebuilt signals",
"x-mcp-name": "signals-firmographics",
"x-mcp-hidden": true,
"x-mcp-description": "Return a company's firmographic profile (headcount, size, type, founded, HQ) plus a Saber-derived refined category and sell-to segments, with cited evidence.",
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "procore.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "The signal reached a terminal `failed` state (the failure rides in the body).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"failed": {
"summary": "Signal ended in a terminal failed state",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "failed",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z",
"error": "Research did not complete because upstream sources were unavailable. Retry later.",
"retryable": true
}
}
}
}
}
},
"201": {
"description": "Signal created and processing (or returned from cache if already completed).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FirmographicsSignalResult"
},
"examples": {
"processing": {
"summary": "Signal accepted, still processing",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "processing",
"domain": "procore.com",
"createdAt": "2026-07-01T10:30:00Z"
}
},
"completed": {
"summary": "Identical request ran recently — existing completed signal returned",
"value": {
"id": "c89f5ae8-c866-7f95-d9af-0f0a70cd0694",
"signalType": "COMPANY",
"namedSignal": "firmographics",
"status": "completed",
"domain": "procore.com",
"answerType": "json_schema",
"createdAt": "2026-07-01T10:30:00Z",
"completedAt": "2026-07-01T10:31:55Z",
"answer": {
"type": "json_schema",
"jsonSchema": {
"name": "Procore Technologies",
"domain": "procore.com",
"website": "https://www.procore.com",
"employeeCount": 3500,
"sizeRange": "1001-5000",
"type": "Public",
"foundedYear": 2002,
"headquarters": {
"city": "Carpinteria",
"state": "CA",
"countryCode": "US"
},
"linkedInFollowers": 320000,
"linkedinIndustry": "Software Development",
"saberCategory": {
"primary": "Construction Software",
"niche": "Construction ERP / project-cost management",
"confidence": 0.92,
"rationale": "Positions itself as a construction management platform for owners, GCs, and specialty contractors.",
"evidence": [
{
"type": "company_site",
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
},
"segments": [
"B2B"
],
"segmentRationale": "Sells to construction firms (owners, general and specialty contractors)."
}
},
"confidence": 0.92,
"sources": [
{
"url": "https://www.procore.com/platform",
"title": "Procore Platform Overview"
}
]
}
}
}
}
}
},
"400": {
"description": "Malformed JSON body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Request parsed but invalid (e.g. bad domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/sync": {
"post": {
"summary": "Create a company signal synchronously",
"description": "**⭐ This is the recommended way to create signals and get results** instead of async + webhooks.\n\nCreates a signal and waits synchronously for it to complete, returning the result in the same request.\nThis endpoint blocks until the signal processing finishes or a timeout is reached.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/companies/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the 12-hour cache (see Caching Behavior on `POST /v1/companies/signals`) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createCompanySignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"operations_hiring": {
"summary": "Check for open operations roles (Quick Start example)",
"value": {
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"answerType": "boolean"
}
},
"crm_detection": {
"summary": "Detect if company uses HubSpot",
"value": {
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"answerType": "boolean"
}
},
"team_size": {
"summary": "Estimate DevOps team size",
"value": {
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"answerType": "number"
}
},
"industry_vertical": {
"summary": "Identify industry vertical",
"value": {
"domain": "clay.com",
"question": "What industry does this company serve?",
"answerType": "open_text"
}
},
"funding_status": {
"summary": "Check venture backing status",
"value": {
"domain": "clay.com",
"question": "Are they venture-backed?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"operations_hiring_response": {
"summary": "Operations hiring check completed",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "clay.com",
"question": "Does this company have open roles in operations?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "boolean",
"boolean": {
"value": true
}
},
"reasoning": "Based on their careers page, Clay has multiple open positions in Operations including Operations Manager and Operations Coordinator roles",
"confidence": 0.95,
"sources": [
{
"url": "https://clay.com/careers",
"title": "Careers at Clay",
"snippet": "We're hiring for Operations Manager and Operations Coordinator roles..."
}
]
}
},
"crm_detection_response": {
"summary": "HubSpot detection completed",
"value": {
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "completed",
"domain": "clay.com",
"question": "Does this company use HubSpot?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:31:45Z",
"answer": {
"type": "boolean",
"boolean": {
"value": false
}
},
"reasoning": "Analysis of website tracking scripts and form integrations indicates Clay uses a custom-built CRM system rather than HubSpot",
"confidence": 0.88,
"sources": [
{
"url": "https://clay.com",
"title": "Clay - Homepage",
"snippet": "No HubSpot tracking scripts detected in site analysis"
}
]
}
},
"team_size_response": {
"summary": "DevOps team size estimated",
"value": {
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "completed",
"domain": "clay.com",
"question": "What is the size of their DevOps team?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:20Z",
"answer": {
"type": "number",
"number": {
"value": 8
}
},
"reasoning": "Based on LinkedIn profiles and job postings, Clay has approximately 8 engineers in DevOps/infrastructure roles",
"confidence": 0.82,
"sources": [
{
"url": "https://www.linkedin.com/company/clay",
"title": "Clay - LinkedIn",
"snippet": "8 employees with DevOps, SRE, or Infrastructure titles"
},
{
"url": "https://clay.com/careers",
"title": "Clay Careers",
"snippet": "Looking to grow our infrastructure team"
}
]
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "clay.com",
"question": "What industry does this company serve?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals": {
"post": {
"summary": "Create a company signal asynchronously",
"x-mcp-description": "Create a company research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a domain and question to create a new research signal. The AI will analyze the domain\nand provide a structured answer based on the specified answer type.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/companies/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/companies/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Caching Behavior\n\nYour response will be cached for up to 12 hours by default, unless you set the `forceRefresh` flag to true.\n\nCache is based on a hash of the following fields: domain (or contactProfileUrl for contact signals), question, answer type, verification mode, and output schema (if provided). Changing any of these fields will create a new signal.\nWe will not send you the webhook if you hit the cache, even if the webhook URL in the payload is different from the original request.\n\nInstead you will receive the latest status of the signal immediately in the response.\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"domain\": \"acme.com\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache (see Caching Behavior above) do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic_question_with_webhook": {
"summary": "Basic company research question with webhook",
"value": {
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"financial_question_with_webhook": {
"summary": "Financial metrics question with webhook",
"value": {
"domain": "techstartup.io",
"question": "What is the company's annual revenue?",
"answerType": "currency",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_question_with_webhook": {
"summary": "Yes/No question with webhook",
"value": {
"domain": "example.com",
"question": "Is this company publicly traded?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"qualification_criteria_with_webhook": {
"summary": "Question with qualification criteria and webhook",
"value": {
"domain": "example.com",
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
},
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"signal_from_template": {
"summary": "Create signal based on template",
"value": {
"domain": "acme.com",
"signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "clay.com",
"question": "What is Clay's main product offering?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"unauthorized": {
"summary": "Missing API key",
"value": {
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"headers": {
"X-Ratelimit-Limit": {
"schema": {
"type": "integer"
},
"description": "Maximum requests allowed per minute",
"example": 100
},
"X-Ratelimit-Remaining": {
"schema": {
"type": "integer"
},
"description": "Requests remaining in current window",
"example": 0
},
"X-Ratelimit-Reset": {
"schema": {
"type": "integer",
"format": "int64"
},
"description": "Unix timestamp when the rate limit resets",
"example": 1234567890
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"retryAfter": 45
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company signals",
"x-mcp-description": "List company signal runs with optional filters for domain, company ID, completion date, status, and subscription.",
"description": "Retrieve all signals with optional filtering by domain or companyId, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating signals and getting results in one step, we recommend using the\n`/v1/companies/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listSignals",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-list",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "comp_abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times for multiple statuses)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
},
{
"name": "subscriptionId",
"in": "query",
"required": false,
"description": "Filter signals by subscription ID (UUID of the signal subscription that triggered execution)",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"paginated_results": {
"summary": "Paginated list of signals",
"value": {
"results": [
{
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Enterprise CRM software"
}
},
"confidence": 0.95
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"domain": "acme.com",
"question": "How many employees?",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"total": 42,
"limit": 25,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/batch": {
"post": {
"summary": "Create company signals in batch",
"x-mcp-description": "Create many company signals across domains, questions, or templates. Use templates for larger standardized batches.",
"description": "Submit multiple signals and domains to create signals using a Cartesian product pattern.\nEach signal is combined with each domain to create individual signals.\n\n**Two ways to create batch signals:**\n1. **Inline Questions**: Define questions directly in the request for ad-hoc research without creating templates\n2. **Using Templates** (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\n\n**Cartesian Product Logic:**\n- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\n- **Sync mode** (default): Maximum 100 resulting signals (templates × domains ≤ 100)\n- **Async mode** (`async: true`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\n\n**Sync Mode (default):**\n- Returns inline results with individual signal statuses\n- Maximum 100 signals per batch\n- Returns 202 with `results[]` array\n\n**Async Mode (`async: true`):**\n- For large batches up to 20,000 signals\n- Returns immediately with a `batchId` for tracking\n- Signals are processed in background chunks\n- Returns 202 with `batchId`, `totalSignals`, `status: \"accepted\"`\n- Poll `/v1/companies/signals/batches/status?domain=...` for progress\n\n**Partial Failure Handling (sync mode):**\n- Individual signal failures don't block other signals from processing\n- Each signal in the response includes its own status (accepted or failed)\n- Returns 202 if at least one signal is scheduled successfully\n- Returns 422 if all signals failed to be scheduled\n\n**Processing:**\n- All signals are processed asynchronously\n- No guarantee of processing order\n- Use the `/v1/companies/signals/sync` endpoint (recommended) to retrieve results as they complete\n\n**Webhooks:**\n- Each signal in the batch can include a `webhookUrl` to receive notifications on completion\n- Webhooks fire individually per signal as each one completes processing\n- Each signal gets one logical delivery, retried on failure — dedupe HTTP requests on the `X-Idempotency-Key` header\n- The delivery body is the signal object itself, with no event envelope — read its `status` (`completed` or `failed`) for the outcome. A Slack webhook URL receives a formatted Slack message instead\n- Deliveries are not signed\n\n**Caching Behavior:**\n- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\n- If a matching cached result exists, the cached response is returned immediately and **no webhook is sent**\n- To force fresh processing (and webhook delivery), set `forceRefresh: true` on individual signals\n\n**Automatic Summary Generation:**\n- Set `generateSummaryOnComplete` to `true` to automatically generate summaries when all signals in the batch complete\n- Summaries are generated per domain (one summary per unique domain in the batch)\n- Summary generation is triggered automatically once all signals for a domain have completed\n- Use the `/v1/companies/signals/summaries` endpoint to retrieve generated summaries\n\n**Credits:** 2 credits per signal created. The batch expands every question across every company — 2 questions × 3 companies creates 6 signals and charges 12 credits. Cached results and failed runs are not charged.\n",
"operationId": "createSignalBatch",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "signal",
"x-docs-group": "Custom company signals",
"x-mcp-name": "company_signals-create_batch",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalBatchRequest"
},
"examples": {
"inline_questions_only": {
"summary": "Inline questions without templates (ad-hoc research)",
"description": "Create signals with custom questions directly without needing to create templates first",
"value": {
"signals": [
{
"question": "What is their main product offering?",
"answerType": "open_text",
"weight": "important"
},
{
"question": "How many employees do they have?",
"answerType": "number",
"weight": "nice_to_have"
},
{
"question": "Are they venture-backed?",
"answerType": "boolean",
"weight": "important"
}
],
"domains": [
"acme.com",
"techcorp.io"
],
"generateSummaryOnComplete": false
}
},
"inline_with_qualification": {
"summary": "Inline questions with qualification criteria",
"description": "Define qualification scoring inline for quick lead qualification",
"value": {
"signals": [
{
"question": "Which CRM are they using?",
"answerType": "list",
"weight": "important",
"qualificationCriteria": {
"choices": {
"salesforce": {
"answerValue": "excellent",
"label": "Salesforce"
},
"hubspot": {
"answerValue": "good",
"label": "HubSpot"
},
"pipedrive": {
"answerValue": "neutral",
"label": "Pipedrive"
},
"none": {
"answerValue": "disqualified",
"label": "No CRM"
}
}
}
},
{
"question": "What is their estimated annual revenue in USD?",
"answerType": "currency",
"weight": "important",
"qualificationCriteria": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 1000000,
"answerValue": "poor"
},
{
"rangeStart": 1000000,
"rangeEnd": 10000000,
"answerValue": "good"
},
{
"rangeStart": 10000000,
"rangeEnd": 999999999999,
"answerValue": "excellent"
}
]
}
},
{
"question": "Are they currently hiring?",
"answerType": "boolean",
"weight": "nice_to_have",
"qualificationCriteria": {
"yes": "good",
"no": "neutral"
}
}
],
"domains": [
"prospect1.com",
"prospect2.com",
"prospect3.com",
"prospect4.com",
"prospect5.com"
],
"generateSummaryOnComplete": true
}
},
"templates_only": {
"summary": "Using templates only (recommended for standardized questions)",
"description": "Use pre-created templates to apply the same research questions across multiple companies",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
},
"templates_with_question_overrides": {
"summary": "Templates with custom question overrides",
"description": "Use templates but override specific questions for customization",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"question": "Are they actively hiring software engineers?"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"question": "What is their primary tech stack for backend services?"
}
],
"domains": [
"saber.app",
"google.com",
"stripe.com",
"shopify.com"
],
"generateSummaryOnComplete": false
}
},
"single_template_qualification": {
"summary": "Single qualification template across many companies",
"description": "Apply one qualification question to research multiple companies at scale",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"domains": [
"company1.com",
"company2.com",
"company3.com",
"company4.com",
"company5.com",
"company6.com",
"company7.com",
"company8.com",
"company9.com",
"company10.com"
],
"generateSummaryOnComplete": true
}
},
"comprehensive_research_batch": {
"summary": "Comprehensive multi-question research (creates summary automatically)",
"description": "Research multiple companies with several questions and generate consolidated summaries",
"value": {
"signals": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"templateId": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
},
{
"templateId": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9g"
},
{
"templateId": "e56f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9g0h"
}
],
"domains": [
"acme.com",
"techcorp.io",
"startup.co"
],
"generateSummaryOnComplete": true
}
}
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted. Response shape depends on whether `async` was set:\n- **Sync mode** (default): Returns `SignalBatchResponse` with inline results\n- **Async mode** (`async: true`): Returns `AsyncBatchResponse` with `batchId` for tracking\n",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/SignalBatchResponse"
},
{
"$ref": "#/components/schemas/AsyncBatchResponse"
}
]
},
"examples": {
"async_batch": {
"summary": "Async batch accepted (async=true)",
"value": {
"batchId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 15000,
"status": "accepted",
"async": true
}
},
"partial_success": {
"summary": "Batch with some failures",
"value": {
"batchId": "batch_abc123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 6,
"accepted": 5,
"rejected": 1,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "saber.app",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "google.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "failed",
"domain": "stripe.com",
"question": "What is their main product?",
"error": "Invalid template configuration"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "saber.app",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "google.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"id": "c89f5he8-h866-7f95-d90f-gf0g7hhdi694",
"status": "processing",
"domain": "stripe.com",
"question": "Are they hiring engineers?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
},
"all_success": {
"summary": "All signals accepted",
"value": {
"batchId": "batch_xyz789ghi012",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 4,
"accepted": 4,
"rejected": 0,
"results": [
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "f56d2ec5-e533-4c62-a67c-dc7d4eeaf361",
"status": "processing",
"domain": "techcorp.io",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "a67d3fc6-f644-5d73-b78d-ed8e5ffbg472",
"status": "processing",
"domain": "startup.co",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
},
{
"templateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"id": "b78e4gd7-g755-6e84-c89e-fe9f6ggch583",
"status": "processing",
"domain": "enterprise.com",
"question": "What is their main product?",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error or all signals in batch failed",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ErrorResponse"
},
{
"$ref": "#/components/schemas/SignalBatchResponse"
}
]
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "domains",
"message": "at least one domain is required"
}
]
}
},
"exceeds_limit": {
"summary": "Exceeds maximum signals",
"value": {
"error": "VALIDATION_ERROR",
"message": "Cartesian product exceeds maximum of 100 signals",
"details": [
{
"field": "batch",
"message": "10 templates × 15 domains = 150 signals, maximum is 100"
}
]
}
},
"all_failed": {
"summary": "All signals failed",
"value": {
"batchId": "batch_fail123def456",
"submittedAt": "2024-01-15T10:30:00Z",
"totalSignals": 2,
"accepted": 0,
"rejected": 2,
"results": [
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "acme.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
},
{
"templateId": "invalid-template-id",
"status": "failed",
"domain": "example.com",
"question": "What is their main product?",
"error": "Template not found or access denied"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/sync": {
"post": {
"summary": "Create a contact signal synchronously",
"description": "**⭐ This is the recommended way to create contact signals and get results** instead of async + webhooks.\n\nCreates a contact signal and waits synchronously for it to complete, returning the result in the same request.\nWorks identically to the company signals sync endpoint but for contact-based signals.\n\n---\n\n## Why Use the Sync Endpoint?\n\n**Advantages over async + webhooks:**\n- ✅ Get results immediately in one request\n- ✅ No webhook hosting required\n- ✅ No polling implementation needed\n- ✅ Simpler integration\n- ✅ Better for synchronous workflows\n\n---\n\n## How It Works\n\n1. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\n2. The endpoint creates the signal and polls for completion\n3. Returns the completed signal with results when processing finishes\n4. If timeout is reached, returns the latest status (processing)\n\n---\n\n## Timeout Configuration\n\n- **Default timeout:** 870 seconds (server-configured, applies when the header is absent)\n- **Max timeout:** 870 seconds — higher values are clamped\n- **Custom timeout:** Use `X-Sbr-Timeout-Sec` header to set custom timeout (up to max)\n- **Error on timeout:** Use `X-Sbr-Timeout-Error: true` header to return 408 on timeout instead of latest status. Exception: after the server has started keep-alive, the status is already 200, so a timeout returns 200 with a non-terminal `status` in the body\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed, or timeout reached with the still-`processing` signal in the body (check `status`)\n- `408 Request Timeout` - Timeout reached (when X-Sbr-Timeout-Error: true) before the server started keep-alive\n- After the first keep-alive byte (whitespace sent to keep a long request open), the status is already `200`: a timeout then returns `200` with a non-terminal `status` in the body, also when X-Sbr-Timeout-Error: true\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignalSync",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
{
"$ref": "#/components/parameters/SyncTimeoutError"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_sync_request": {
"summary": "Synchronous contact signal creation",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Signal completed, or — if the timeout elapsed — still `processing` (check the body's `status`, not the HTTP code).",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL"
}
}
}
]
},
"examples": {
"completed_signal": {
"summary": "Contact signal completed within timeout",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"confidence": 0.9
}
},
"timeout_still_processing": {
"summary": "Timeout reached, signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"408": {
"description": "Request Timeout (only when the `X-Sbr-Timeout-Error` header is \"true\"),\nand only when the timeout occurs before the server starts keep-alive.\nOn a long request the server can send keep-alive whitespace to keep the\nconnection open. The first keep-alive byte commits the `200` status, so\na later timeout returns `200` with a non-terminal `status` (for example\n`processing`) in the body. Treat that response as a timeout too.\n",
"content": {
"application/json": {
"example": {}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals": {
"post": {
"summary": "Create a contact signal asynchronously",
"x-mcp-description": "Create a contact research signal, return immediately, then poll the signal ID or receive results through a webhook.",
"description": "Submit a LinkedIn URL and question to create a new research signal for an individual contact.\nThe AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## When to Use This Endpoint\n\nUse this endpoint for **event-driven architectures** where you need webhook notifications.\nFor simpler integrations, use the `/v1/contacts/signals/sync` endpoint (recommended) which returns results immediately.\n\n---\n\n## Webhooks\n\nConfigure webhooks by including a `webhookUrl` parameter in your request.\n\n**What we deliver:**\nOne logical delivery per signal, once it reaches a terminal state — retries\n(below) mean your endpoint may see it as more than one HTTP request, all\ncarrying the same `X-Idempotency-Key`. The body is the signal object itself —\nthere is no event envelope and no event-type header, so read the body's\n`status` (`completed` or `failed`) to tell the outcome. One exception: a\nSlack webhook URL (`https://hooks.slack.com/services/…`) receives a formatted\nSlack message instead of the raw signal object.\n\n**Webhook security:**\nDeliveries are not signed. Treat the payload as untrusted: use an unguessable\n`webhookUrl`, and re-read the signal via `GET /v1/contacts/signals/{signalId}`\nbefore acting on it. Each delivery carries an `X-Idempotency-Key` header you can\nuse to discard duplicates.\n\n**Webhook reliability:**\n- Deliveries that fail to connect, time out, or return 5xx are attempted up to 5 times in total with exponential backoff\n- A 4xx response is treated as a permanent rejection and is not retried\n- A `webhookUrl` whose host does not resolve in DNS is rejected without retry, so point it at a hostname that already resolves\n- Webhook requests timeout after 25 seconds\n- Your endpoint should respond with 2xx status code\n- A failed delivery never fails the signal: the answer is still produced, billed, and readable via the API\n\n---\n\n## Using with Templates\n\nUse `signalTemplateId` instead of defining questions inline for standardized research:\n\n```json\n{\n \"contactProfileUrl\": \"https://linkedin.com/in/johndoe\",\n \"signalTemplateId\": \"a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d\",\n \"webhookUrl\": \"https://myapp.com/webhooks/signals\"\n}\n```\n\nTemplates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via `POST /v1/companies/signals/templates`.\n\n---\n\nContact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\n\n---\n\n## Credits\n\nEach signal charges **2 credits**. Repeat calls served from the cache do not consume additional credits, and failed runs are never charged — reserved credits are released.\n",
"operationId": "createContactSignal",
"deprecated": true,
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-create_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic_contact_signal_with_webhook": {
"summary": "Basic contact research question with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"answerType": "open_text",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
},
"boolean_contact_signal_with_webhook": {
"summary": "Yes/No question about contact with webhook",
"value": {
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"question": "Is this person actively posting on LinkedIn?",
"answerType": "boolean",
"webhookUrl": "https://myapp.com/webhooks/signals"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact signal created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalResponse"
},
"examples": {
"created_signal": {
"summary": "Newly created contact signal",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/401"
},
"429": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/429"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/post/responses/500"
}
}
},
"get": {
"summary": "List contact signals",
"x-mcp-description": "List contact signal runs with optional filters for profile URL and pagination.",
"description": "Retrieve all contact signals with optional filtering by LinkedIn URL, pagination and filtering.\nResults are sorted by creation date (latest first).\n\n**Note:** For creating contact signals and getting results in one step, we recommend using the\n`/v1/contacts/signals/sync` endpoint instead, which returns the result directly.\n",
"operationId": "listContactSignals",
"x-docs-group": "Custom contact signals",
"x-mcp-name": "contacts-list_signals",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "contactProfileUrl",
"in": "query",
"required": false,
"description": "Filter signals by contact profile URL (LinkedIn or other professional profile)",
"schema": {
"type": "string",
"format": "uri"
},
"example": "https://linkedin.com/in/johndoe"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Contact signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"400": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/400"
},
"401": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/401"
},
"500": {
"$ref": "#/paths/~1v1~1companies~1signals/get/responses/500"
}
}
}
},
"/v1/companies/signals/{signalId}": {
"get": {
"summary": "Get a company signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a company signal when it has completed.",
"description": "Retrieve the current status and results of a signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-get",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDetailResponse"
},
"examples": {
"pending_signal": {
"summary": "Signal still processing",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "processing",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_signal": {
"summary": "Completed signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Acme Corp is a leading provider of enterprise software solutions, specializing in customer relationship management and business automation tools."
}
},
"reasoning": "Based on the company's website and public information, Acme Corp specializes in enterprise software.",
"confidence": 0.95,
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
},
{
"url": "https://acme.com/products",
"title": "Our Products",
"snippet": "Customer relationship management solutions..."
}
],
"metadata": {
"processingTime": "2m 15s",
"sourcesAnalyzed": 12
}
}
},
"failed_signal": {
"summary": "Failed signal with error",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "failed",
"domain": "acme.com",
"question": "What is Acme Corp's main business focus?",
"createdAt": "2024-01-15T10:30:00Z",
"error": "Unable to access domain or insufficient information available"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Signal not found",
"value": {
"error": "NOT_FOUND",
"message": "Signal not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/logs": {
"get": {
"summary": "List signal executions for a subscription",
"x-mcp-description": "List company signal executions produced by a specific signal subscription, with filters and pagination.",
"description": "Retrieve all signal executions that were triggered by a specific signal subscription.\nReturns the same paginated response as `GET /v1/companies/signals` but filtered to a single subscription.\nResults are sorted by creation date (latest first).\n",
"operationId": "listSubscriptionLogs",
"deprecated": true,
"x-docs-group": "Signal results",
"x-mcp-name": "company_signals-subscription_logs",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The UUID of the signal subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "domain",
"in": "query",
"required": false,
"description": "Filter signals by company domain",
"schema": {
"type": "string"
},
"example": "acme.com"
},
{
"name": "companyId",
"in": "query",
"required": false,
"description": "Filter signals by company ID",
"schema": {
"type": "string"
},
"example": "b23c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 25
},
"example": 25
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "fromDate",
"in": "query",
"description": "Filter signals completed on or after this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-01-01T00:00:00Z"
},
{
"name": "toDate",
"in": "query",
"description": "Filter signals completed on or before this date (RFC3339 format)",
"schema": {
"type": "string",
"format": "date-time"
},
"example": "2024-12-31T23:59:59Z"
},
{
"name": "status",
"in": "query",
"description": "Filter by signal status (can be specified multiple times)",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"style": "form",
"explode": true,
"example": [
"completed"
]
}
],
"responses": {
"200": {
"description": "Subscription logs retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of signal detail responses",
"items": {
"$ref": "#/components/schemas/SignalDetailResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals matching the filters",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 25
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
}
}
}
},
"400": {
"description": "Bad Request - Invalid subscription ID format",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Subscription does not exist or does not belong to this organization",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/signals/{signalId}": {
"get": {
"summary": "Get a contact signal by ID",
"x-mcp-description": "Retrieve the current status and AI-generated answer for a contact signal when it has completed.",
"description": "Retrieve the current status and results of a contact signal. If the signal is completed,\nthe response will include the AI-generated answer, confidence score, and sources.\n",
"operationId": "getContactSignal",
"x-docs-group": "Signal results",
"x-mcp-name": "contacts-get_signal",
"x-mcp-hidden": true,
"tags": [
"Signals"
],
"parameters": [
{
"name": "signalId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact signal (UUID format)",
"schema": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"responses": {
"200": {
"description": "Contact signal retrieved successfully",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/SignalDetailResponse"
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL (LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
}
}
}
]
},
"examples": {
"completed_contact_signal": {
"summary": "Completed contact signal with results",
"value": {
"id": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250",
"status": "completed",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"question": "What are this person's main professional interests?",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:15Z",
"answer": {
"type": "open_text",
"openText": {
"value": "Software engineering, AI/ML, and developer tools"
}
},
"reasoning": "Based on LinkedIn profile analysis, this person regularly engages with content about software engineering and AI/ML topics.",
"confidence": 0.9,
"sources": [
{
"url": "https://linkedin.com/in/johndoe",
"title": "John Doe - LinkedIn",
"snippet": "Passionate about building AI-powered developer tools..."
}
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Signal not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom": {
"post": {
"summary": "Enrich a company with a custom question",
"description": "Entity-first alias of `POST /v1/companies/signals` (Phase 2): the same\nresearch engine, cache, and **price**, returning the\n`enrichment_run` envelope instead of the bare signal.\n\n### Completion semantics\n\n- If the run is already complete when the request returns (served\n from the shared answer cache), the response is `200` with the\n result inline.\n- A fresh run returns `202 Accepted` with\n `Location: /v1/enrichment-runs/{runId}` — poll that resource (or\n supply a `webhookUrl`). This surface never long-polls; callers who\n want blocking behaviour keep `POST /v1/companies/signals/sync`\n until its sunset.\n\nEntity-first means the identifier is fixed by the route: `domain` is\nrequired and `contactProfileUrl` is rejected here.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\nSame pricing as `POST /v1/companies/signals`: **2 credits** per run,\nrepeat calls served from the cache do not consume additional credits,\nand failed runs are never charged. The run's `credits` block (on run\nreads) states what your organization was actually charged.\n",
"operationId": "createCompanyEnrichCustom",
"x-mcp-name": "companies-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a company",
"value": {
"domain": "acme-corp.com",
"question": "Does this company use Kubernetes in production?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing domain, contactProfileUrl on the company surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/custom/batch": {
"post": {
"summary": "Enrich many companies with one custom question",
"description": "One shared question applied to a `targets` array of domains — one\n`enrichment_run` per target, **no question × company expansion**\n(unlike the legacy `/v1/companies/signals/batch`). Capped at 100\ntargets; larger workloads stay on the legacy async batch surface.\nEither `question` or `signalTemplateId` must be provided.\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/companies/signals`, so each callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685).\n\n## Credits\n\n**2 credits per run** (per target), success-only, cached results\nfree — identical to single custom enrichment.\n",
"operationId": "createCompanyEnrichCustomBatch",
"x-mcp-name": "companies-enrich_custom_batch",
"x-mcp-description": "Enrich many companies with one shared custom question — one run per\ntarget domain. Capped at 100 targets per call; split a larger list\ninto successive calls of 100 or fewer. Returns 202 immediately; poll\neach run with enrichment_runs-get or list them with enrichment_runs-list.\n",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-credit-cost-unit": "run",
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchRequest"
}
}
}
},
"responses": {
"202": {
"description": "Batch accepted; one run per accepted target.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunBatchResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (empty targets, more than 100 targets, neither question nor signalTemplateId, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/custom": {
"post": {
"summary": "Enrich a contact with a custom question",
"description": "Entity-first alias of `POST /v1/contacts/signals`: same engine, cache,\nand **price**, `enrichment_run` envelope on the wire. The identifier\nis fixed by the route: `contactProfileUrl` is required and `domain`\nis rejected here. Completion semantics match the company surface\n(`200` inline on cache serve, `202` + `Location` for fresh runs).\n\n**Webhook payload:** `webhookUrl` uses the same delivery path as\n`/v1/contacts/signals`, so the callback body is the legacy signal\nshape (top-level `answer`, no run fields) — **not** the\n`enrichment_run` envelope. A run-shaped webhook payload arrives with\nthe sunset plan (SEL-3685); until then, poll\n`/v1/enrichment-runs/{runId}` for the run shape.\n\n## Credits\n\n**2 credits** per run; cached repeats free; failed runs never charged.\n",
"operationId": "createContactEnrichCustom",
"x-mcp-name": "contacts-enrich_custom",
"x-credit-feature": "api-post-companies-signals",
"x-credit-cost": 2,
"x-docs-group": "Custom enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactSignalRequest"
},
"examples": {
"basic": {
"summary": "Ask a custom question about a contact",
"value": {
"contactProfileUrl": "https://www.linkedin.com/in/someone",
"question": "Has this person changed jobs in the last 6 months?",
"answerType": "boolean"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header.",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing contactProfileUrl, domain on the contact surface, malformed fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/resolve-domain": {
"post": {
"summary": "Resolve a company name to its official website domain",
"operationId": "resolveCompanyDomain",
"x-credit-feature": "resolve-company-domain",
"x-credit-cost": 0.1,
"x-mcp-name": "companies-resolve-domain",
"x-mcp-timeout-ms": 270000,
"x-mcp-description": "Resolve a company name to its official domain. Supply country and article context for ambiguous names. Branch on status; ambiguous candidates are alternatives, not a ranked answer. Allow up to four minutes.",
"tags": [
"Companies"
],
"description": "Researches a company name with optional country and article context.\nNo domain is required. This does not create or enrich a company identity.\nExact repeats of a normalized request in the same organization can reuse\na `resolved` result for seven days. The cache is not shared across\norganizations. Changes to the company name, country, context or resolver\nversion cause a cache miss. Other outcomes are not cached. Raw input\ncontext is not stored in the cache, and cache entries do not create a\ncanonical company identity.\n\nResolves the company behind the supplied name. For a brand or product,\nreturns its operating company and that company's primary domain, which may\nalso host its main product. About, investor-relations and regional sites\nare evidence, not automatic replacements for the primary domain.\nA named company or subsidiary is not replaced by its parent group.\nCandidate `companyName` identifies the resolved company; `matchReason`\nexplains any brand or product relationship. Parent-company details are\nnot returned.\nOptional `companyInfoUrl` is an official About or company-overview page\nfetched during the run, possibly on a different domain. It is null when\nunavailable; research is not extended solely to populate it.\nOptional `companyDescription` summarizes each candidate's business from\ncaptured sources. The agent is guided to use at most five concrete\nsentences, without an enforced length limit. It is null when unsupported. Input context\nalone is not verified fact, and research is not extended solely to write\na description.\n\nBranch on `status`:\n- `resolved`: one supported candidate, with its hostname in `domain`.\n- `ambiguous`: two or three plausible candidates; `domain` is null.\n Supply more context rather than selecting the first candidate.\n- `not_found`: completed research found no supported domain. This does\n not mean that the company has no website.\n- `incomplete`: research could not establish a result within the available\n tools or budget. `domain` is null and `candidates` is empty.\n\nEvidence URLs come from sources captured during the original research. They support\nthe agent's match assessment, not a guarantee of website availability.\nEvidence can be empty if no citation survives source validation. Citation\nloss alone does not fail a resolution; unavailable citations are omitted.\nParent, sponsor and similarly named companies are not interchangeable.\nProvide country and article context for short or ambiguous company names.\n\nThe synchronous request has a 240-second deadline. The workflow has a\n215-second execution limit, leaving time for admission, billing, workflow\nstartup and result delivery. A timeout returns 408, not `not_found`.\nReaching a wall-clock deadline does not automatically produce `incomplete`.\nProvider or execution failures return 500.\nEach page fetch has a 60-second total limit so a slow website does not\nconsume the entire request. The agent can use another source or return\n`incomplete` when research cannot finish.\n\nCache hits are free and do not start research. On a cache miss, completed\n`resolved`, `ambiguous` and `not_found` results cost 0.1 credits per request.\n`incomplete` results and errors are free. Insufficient credits return 402\nbefore research starts on a miss.\n\nBy default, each organization can run up to 10 resolutions concurrently,\nshared across all its API keys and MCP calls. This limit is configurable\nper deployment. Requests above this limit return 429 with `Retry-After`\nbefore research starts or credits are charged.\n",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionRequest"
},
"example": {
"companyName": "NT1 Pty Ltd",
"countryCode": "AU",
"context": "Australian mineral exploration company in the Plutonian II transaction."
}
}
}
},
"responses": {
"200": {
"description": "Domain resolution outcome",
"headers": {
"Cache-Status": {
"description": "`SaberCompanyDomain; hit` for a cached result; `SaberCompanyDomain; fwd=uri-miss` for fresh research.",
"schema": {
"type": "string"
},
"examples": {
"hit": {
"value": "SaberCompanyDomain; hit"
},
"miss": {
"value": "SaberCompanyDomain; fwd=uri-miss"
}
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyDomainResolutionResult"
}
}
}
},
"400": {
"description": "Request body could not be parsed"
},
"401": {
"description": "Authentication is missing or invalid"
},
"402": {
"description": "Insufficient credits; no research or charge"
},
"408": {
"description": "Resolution exceeded its deadline"
},
"422": {
"description": "Company name, country code or context is invalid"
},
"429": {
"description": "Request rate or organization concurrency limit exceeded; no charge",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying",
"schema": {
"type": "integer"
}
}
}
},
"500": {
"description": "Research failed or did not produce a valid result"
}
}
}
},
"/v1/companies/enrich/firmographics": {
"post": {
"summary": "Enrich a company with firmographics",
"description": "Prebuilt enrichment: company profile, refined category, and customer\nsegments — the same research contract as `POST /v1/signals/firmographics`,\non the entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FirmographicsSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`firmographics` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.6 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFirmographics",
"x-mcp-name": "companies-enrich_firmographics",
"x-credit-feature": "enrich-firmographics",
"x-credit-cost": 0.6,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Firmographics for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/funding": {
"post": {
"summary": "Enrich a company with funding status",
"description": "Prebuilt enrichment: a company's most recent funding and public/IPO\nstatus — the same research contract as `POST /v1/signals/funding`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `FundingSignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`funding` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.3 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichFunding",
"x-mcp-name": "companies-enrich_funding",
"x-credit-feature": "enrich-funding",
"x-credit-cost": 0.3,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Funding for a single company",
"value": {
"domain": "stripe.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/mna": {
"post": {
"summary": "Enrich a company with M&A activity",
"description": "Prebuilt enrichment: a company's recent M&A activity, as acquirer or\nacquiree — the same research contract as `POST /v1/signals/mna`, on\nthe entity-first surface with the `enrichment_run` envelope and its\ncompletion semantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). The completed\n`result.jsonSchema` conforms to `MAndASignalAnswer` — that\nsub-field only, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`mna` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichMna",
"x-mcp-name": "companies-enrich_mna",
"x-credit-feature": "enrich-mna",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "M&A activity for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/open-jobs": {
"post": {
"summary": "Enrich a company with open jobs",
"description": "Prebuilt enrichment: a company's current hiring — total open roles and\nexample titles — the same research contract as\n`POST /v1/signals/open-jobs`, on the entity-first surface with the\n`enrichment_run` envelope and its completion semantics (`200` inline\non a cache serve, `202` + `Location: /v1/enrichment-runs/{runId}` for\na fresh run). The completed `result.jsonSchema` conforms to\n`OpenJobsSignalAnswer` — that sub-field only, not the whole `result`\n(which also carries status/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key (`open-jobs` here; `tech` for\n`tech-stack`), not this surface's `type` vocabulary. The webhook\nmoves to the run shape with the sunset plan (SEL-3685); until then\npoll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.5 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichOpenJobs",
"x-mcp-name": "companies-enrich_open_jobs",
"x-credit-feature": "enrich-open-jobs",
"x-credit-cost": 0.5,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrebuiltSignalRequest"
},
"examples": {
"basic": {
"summary": "Open jobs for a single company",
"value": {
"domain": "acme-corp.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/enrich/tech-stack": {
"post": {
"summary": "Enrich a company with its tech stack",
"description": "Prebuilt enrichment: which system(s) a company currently uses — the\nsame research contract as `POST /v1/signals/tech`, on the entity-first\nsurface with the `enrichment_run` envelope and its completion\nsemantics (`200` inline on a cache serve, `202` +\n`Location: /v1/enrichment-runs/{runId}` for a fresh run). Provide\nexactly one of `category` (survey a category: `erp` or `crm`) or\n`technology` (verify one specific technology). The completed\n`result.jsonSchema` conforms to `TechSignalAnswer` — that sub-field\nonly, not the whole `result` (which also carries\nstatus/sources/reasoning).\n\n**Webhook payload:** `webhookUrl` delivers the legacy signal shape\n(top-level `answer`, no run fields), and its `namedSignal` field\ncarries the internal catalogue key — `tech`, not this surface's\n`tech-stack` — so it is NOT a value the `type` vocabulary admits. The\nwebhook moves to the run shape with the sunset plan (SEL-3685); until\nthen poll `/v1/enrichment-runs/{runId}` for the public shape.\n\n## Credits\n\n**0.75 credits**, charged once per result delivered to your\norganization. Repeat calls served from the cache are free — including\nresults your organization already paid for via the legacy signals\nsurface — and failed runs are never charged.\n",
"operationId": "createCompanyEnrichTechStack",
"x-mcp-name": "companies-enrich_tech_stack",
"x-credit-feature": "enrich-tech-stack",
"x-credit-cost": 0.75,
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TechSignalRequest"
},
"examples": {
"category": {
"summary": "Which CRM does this company use?",
"value": {
"domain": "acme-corp.com",
"category": "crm"
}
},
"technology": {
"summary": "Does this company use Salesforce?",
"value": {
"domain": "acme-corp.com",
"technology": "salesforce"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Run already complete (cache serve) or terminally failed — the failure and its `retryable` flag ride in the body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"202": {
"description": "Fresh run started; poll the `Location` header (`/v1/enrichment-runs/{runId}`).",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "Poll path of the created run."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"400": {
"description": "Malformed JSON in the request body.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain, missing/conflicting `category`/`technology`, unknown technology).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/enrich/work-email": {
"post": {
"summary": "Find a contact's verified work email",
"description": "The email finder on the enrichment family (decision 16): the SAME\nlookup as `POST /v1/contacts/find-email` — identical request and\nresponse contract, same verification semantics, same 0.1-credit\nprice. This rename is a name change, not a reprice. The legacy path\nkeeps its 0.1-credit price until its announced sunset; use this route\ngoing forward.\n\nSee `POST /v1/contacts/find-email` for the full response semantics\n(`status` discriminator, catch-all handling, name parsing).\n\n## Credits\n\nEach call charges **0.1 credits** on success — both when a\ndeliverable email is found and when no email is found (the\nverification attempt itself consumed work). Requests rejected\nbefore any verification work begins are not charged; insufficient\nbalance returns `402` before any work is performed.\n",
"operationId": "findWorkEmail",
"x-mcp-name": "contacts-enrich_work_email",
"x-credit-feature": "enrich-work-email",
"x-credit-cost": 0.1,
"x-docs-group": "Contact enrichment",
"tags": [
"Enrichments"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailRequest"
},
"examples": {
"basic": {
"summary": "Basic two-token name",
"value": {
"full_name": "Joey Ommen",
"domain": "saber.app"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Processed within budget — a found email or explicit nulls for the not-found case.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FindEmailResponse"
}
}
}
},
"400": {
"description": "Malformed request body (invalid JSON, missing required fields)"
},
"401": {
"description": "Missing or invalid authentication"
},
"402": {
"description": "Insufficient credits — the organization's balance cannot cover the 0.1-credit charge. No verification work was performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (unparseable name, invalid domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded. Two distinct sources fold into this\nstatus code:\n - **Per-API-key rate limit** (our middleware) — uses the\n flat `RateLimitErrorResponse` envelope.\n - **Upstream Emailable throttle** — uses the standard\n error envelope; the response includes a `Retry-After`\n header (typically `30`) you can honor before retrying.\n\nFor either source, retry after `Retry-After` seconds. The\ncustomer-side retry semantics are the same.\n",
"headers": {
"Retry-After": {
"schema": {
"type": "string",
"example": "30"
},
"description": "Seconds to wait before retrying. Always present on the\nupstream-throttle response; present on the middleware\nresponse when set by the global rate limiter.\n"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RateLimitErrorResponse"
}
}
}
},
"502": {
"description": "Emailable returned a persistent upstream error (every\ncold-path candidate failed to verify).\n"
}
}
}
},
"/v1/enrichment-types": {
"get": {
"summary": "List enrichment types",
"description": "The machine-readable catalogue of typed enrichments (decision 18):\none entry per prebuilt type served on `POST\n/v1/companies/enrich/{type}` plus the contact-side `work-email`\nenrichment. Each entry carries its stable `key`, `entity`,\n`displayName`, the OpenAPI component its completed answer conforms\nto (`resultSchema.name` — the schema itself lives in this document,\nreferenced rather than duplicated, with `resultSchema.version`\ncounting content revisions of the type's answer contract; WHERE the\nanswer sits depends on `executionMode`, so key off it — see\n`resultSchema`), `executionMode` (`async` = `202` + run polling with\n`200` inline on a cache serve, answer at `result.jsonSchema`; `sync`\n= the answer arrives as the whole create-response body), and\n`deprecationState` (`active` today; sunset states join with the\ndeprecation plan).\n\n`price` is the **standard** rate card price (`credits`) and the card\nversion it was published from — per-organization overrides agreed\nwith sales apply at charge time and are not reflected here. The\ncatalogue is generated from the same rate card that enforcement\nuses, and a build gate keeps the two in lockstep.\n\nThe free-form `enrich/custom` surface is not an enrichment type —\nits question and result schema are caller-defined per request.\n\nReads are free. The catalogue is static per deploy: cache freely,\nand treat unknown fields and unknown `deprecationState` values as\nforward-compatible additions.\n",
"operationId": "listEnrichmentTypes",
"x-mcp-name": "enrichment_types-list",
"x-docs-group": "Prebuilt enrichment",
"tags": [
"Enrichments"
],
"responses": {
"200": {
"description": "The catalogue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentTypesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs": {
"get": {
"summary": "List enrichment runs",
"description": "Unified list across company and contact runs. Filters are the common\nfields only: `entityType`, `status`, `fromDate`/`toDate` (against the\nrun's **creation** time), exact `entityId` (the resolved company id —\ncontact runs carry no entity id at rest, so an `entityId` filter\nnever matches them), and `type` (`custom` or a prebuilt enrichment\ntype). No per-entity attribute filters by design; look a company up\nby id, not by domain.\n\nReads are free. Each run carries a `credits` block stating what your\norganization was charged for it — `none` means it was served free\n(for example a cache hit).\n",
"operationId": "listEnrichmentRuns",
"x-mcp-name": "enrichment_runs-list",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "entityType",
"in": "query",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "entityId",
"in": "query",
"schema": {
"type": "string"
},
"description": "Exact company id; never matches contact runs."
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"enum": [
"custom",
"firmographics",
"funding",
"mna",
"open-jobs",
"tech-stack"
]
},
"description": "Run type — `custom` or a prebuilt enrichment type."
},
{
"name": "status",
"in": "query",
"style": "form",
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
}
},
"description": "Repeatable. Unknown values are rejected with `422`."
},
{
"name": "fromDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or after this instant."
},
{
"name": "toDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
},
"description": "Include runs created at or before this instant."
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Runs retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRunListResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (bad `entityType`, unknown `status`, non-RFC3339 dates, out-of-range `limit`/`offset`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-runs/{runId}": {
"get": {
"summary": "Get an enrichment run",
"description": "The run resource is the source of truth for async enrichment: poll it\nafter a `202` (its path arrives in the `Location` header), or after a\nmissed webhook. Free; includes the `credits` block.\n",
"operationId": "getEnrichmentRun",
"x-mcp-name": "enrichment_runs-get",
"x-docs-group": "Runs",
"tags": [
"Enrichments"
],
"parameters": [
{
"name": "runId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No run with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (`runId` is not a UUID).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded — retry after the window resets.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/summaries": {
"post": {
"summary": "Generate a company summary",
"description": "Generates a summary of the completed enrichments for a domain — the\nsame operation as `POST /v1/companies/signals/summaries`, on its new\nhome (decision 26: the API resource is **summaries**; \"Brief\" stays\nthe in-app word). The legacy route stays free until its announced\nsunset; use this route going forward.\n\nReturns `204` when the domain has no completed enrichments to\nsummarize, and `409` when a summary for the domain is already being\ngenerated.\n\n## Credits\n\n**0.25 credits**, charged only when a summary is FRESHLY generated\n(a `200` with new content): `204`, `409`, errors, and repeat calls\nserved from the stored summary (no new enrichments completed since\nit was written) never charge. A summary is one LLM pass over\nenrichments you already paid for — priced below the cheapest\nprebuilt enrichment type (decision 20), and billed once per pass,\nhowever many times you re-read it.\n",
"operationId": "generateCompanySummary",
"x-mcp-name": "companies-generate_summary",
"x-credit-feature": "company-summaries",
"x-credit-cost": 0.25,
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
}
}
}
},
"responses": {
"200": {
"description": "Summary generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
}
}
}
},
"204": {
"description": "No completed enrichments to summarize — nothing generated, nothing charged."
},
"402": {
"description": "Insufficient credits — no work performed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "A summary for this domain is already being generated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (missing or malformed domain).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List company summaries",
"description": "Lists previously generated summaries for a domain, newest first —\nthe same operation as `GET /v1/companies/signals/summaries`, on its\nnew home. Reads are free.\n",
"operationId": "listCompanySummaries",
"x-mcp-name": "companies-list_summaries",
"x-docs-group": "Summaries",
"tags": [
"Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Summaries retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"422": {
"description": "Validation failed (missing domain parameter).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates": {
"post": {
"summary": "Create an enrichment template",
"description": "Creates a reusable custom-question template — the same operation as\n`POST /v1/companies/signals/templates`, promoted to its top-level\nhome (decision 26: templates are entity-agnostic; contact\nenrichments reference the same `signalTemplateId`). The legacy route\nkeeps working until its announced sunset. Free.\n",
"operationId": "createEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-create",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List enrichment templates",
"description": "Lists your organization's templates. Free.",
"operationId": "listEnrichmentTemplates",
"x-mcp-name": "enrichment_templates-list",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved. Unparseable pagination values fall back to the defaults rather than erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
}
}
}
},
"/v1/enrichment-templates/{templateId}": {
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get an enrichment template",
"description": "Free.",
"operationId": "getEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-get",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"200": {
"description": "Template retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update an enrichment template",
"description": "Updating creates a new template version; in-flight runs keep the\nversion they started with. Same operation as the legacy\n`PATCH /v1/companies/signals/templates/{templateId}`. Free.\n",
"operationId": "updateEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-update",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete an enrichment template",
"description": "Free.",
"operationId": "deleteEnrichmentTemplate",
"x-mcp-name": "enrichment_templates-delete",
"x-docs-group": "Enrichment templates",
"tags": [
"Enrichment Templates"
],
"responses": {
"204": {
"description": "Template deleted."
},
"404": {
"description": "No template with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors": {
"post": {
"summary": "Create a monitor",
"description": "Creates a standing watch that emits **signals** over time (decision\n25). The create body carries `checks: [{kind, …}]` — exactly ONE\ncheck at launch; the array is the forward contract for multi-check\nmonitors later.\n\n- `{kind: \"enrichment\", templateId}` + `target: {type: \"list\",\n listId, entity}` (+ optional `schedule`) — a **watch** monitor:\n re-runs the template's question against every entity on the list\n and emits a `result_changed` signal when an answer changes.\n- `{kind: \"signal\", type, prompt?, filters?}` (+ optional `webhook`) —\n a **discovery** monitor: watches the market for occurrences of\n the given type (`job_posted`, `social_posted`, `funding_raised`,\n `investment_made`, `ipo_announced`, `ipo_stage_changed`, `ipo_rumoured`,\n `repo_engaged`, `tender_published`). Omit `webhook` to keep the\n results in Saber: the signals are still detected, stored and charged,\n and are read from `GET /v1/monitors/{monitorId}/signals`.\n\n `repo_engaged` watches the GitHub repositories and organizations you\n name and emits one occurrence per person who opens an issue or a\n pull request there, resolved to the company they work for. It\n requires an active GitHub connector on your organization: without\n one, the create is rejected with 422 rather than accepted as a\n monitor that can never run.\n\n The occurrence payload names the person as `author.login`,\n `author.name` and `author.profileUrl` (their GitHub profile). It also\n carries `author.linkedinUrl` when the author was identified through\n your own Sales Navigator connector. The value is the person's public\n LinkedIn profile (`https://www.linkedin.com/in/...`). We search for\n the author by name at the employer the signal names; when no source\n names the employer, we search by name and confirm the match from the\n GitHub profile. The key is absent whenever no single person could be\n named: for example, when you have no Sales Navigator connector, when\n we hold no LinkedIn page for that employer, when the GitHub profile\n has no first and last name, when more than one person of that name\n works there, when LinkedIn holds no public profile for that person,\n or when no search for this author has answered yet (for example, the\n search failed or was deferred to a later cycle). An answer found earlier is reused for\n later signals from the same author. Naming the wrong individual is\n worse than naming none.\n\n `tender_published` watches public procurement portals — TED (EU),\n SAM.gov (US federal) and UK Contracts Finder — and emits one\n occurrence per OPEN notice matching your filters. Award notices and\n notices past their submission deadline are never emitted: they\n describe a contract you can no longer bid on. Filter with CPV and\n NAICS codes rather than keywords, because a notice's description is\n published in the buyer's own language and is not translated.\n\n Its scope is software, AI and automation work only. Saber reads only\n CPV divisions 72, 48, 30 and 73 (TED, UK Contracts Finder) and NAICS\n 541511, 541512, 541519 and 513210 (SAM.gov), and delivers a notice\n only when its classifier finds software, AI or automation work in\n it. `cpvCodes` and `naicsCodes` narrow this scope; they cannot widen\n it. `minValueEur` compares only values stated in EUR: a notice with\n no stated value, or a value in another currency, passes it. This\n type is not yet available in production; until it is enabled, a\n create returns 422.\n\n**Upsert on the (template, list) pair — not a no-op.** An enrichment\ncheck for a template + list that already has a monitor does NOT\ncreate a duplicate; it **replaces that monitor's configuration with\nthis request's values**. Because the create body fills defaults,\nfields you omit are *cleared*, not preserved: omitting `schedule`\nnulls the existing frequency/cron and resets the timezone to UTC (so\nthe monitor stops firing while its status stays active), and omitting\n`name` clears it. The response is `201` with the pre-existing monitor's\n`id`/`createdAt`. To change one field on an existing monitor, use\n`PATCH /v1/monitors/{monitorId}` (partial update) rather than a\nre-`POST`. (A future revision may `409` this collision instead —\ntracked in SEL-3916.)\n\n## Credits\n\nMonitor configuration, lifecycle, and reads are free. Each signal a\nmonitor EMITS is charged under the `monitor-signals` feature (see\n/pricing for the current rate; per-organization overrides apply) — a\nwatch run that detects no change emits nothing and costs nothing.\nThe legacy subscription routes keep working (and keep their pricing)\nuntil their announced sunset.\n\nPricing follows the surface that CREATED the underlying\nsubscription: when this endpoint's upsert returns a pre-existing\nsubscription originally created on a legacy route, that monitor\nkeeps its legacy per-run pricing (including charging for no-change\nwatch runs). Delete it and re-create it here to move it to\nper-emission pricing.\n",
"operationId": "createMonitor",
"x-mcp-name": "monitors-create",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMonitorRequest"
},
"examples": {
"discovery": {
"summary": "Watch the market for funding rounds",
"value": {
"name": "Funding watch",
"checks": [
{
"kind": "signal",
"type": "funding_raised",
"prompt": "Series A+ rounds in European fintech"
}
],
"webhook": {
"url": "https://myapp.com/webhooks/signals"
}
}
},
"watch": {
"summary": "Watch a list with an enrichment template",
"value": {
"name": "CRM change watch",
"checks": [
{
"kind": "enrichment",
"templateId": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
}
],
"target": {
"type": "list",
"listId": "7f5c1dc4-d422-4b51-956b-cb6d3ddaf250",
"entity": "company"
},
"schedule": {
"frequency": "weekly"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Monitor created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"422": {
"description": "Validation failed (zero or multiple checks, unknown signal type, missing target/webhook).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitors",
"description": "Lists monitors of ALL kinds by default (decision 3); narrow with\nplain `?kind=watch|discovery` or `?target=list|market` filters\n(decision 25). Newest first.\n",
"operationId": "listMonitors",
"x-mcp-name": "monitors-list",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "kind",
"in": "query",
"schema": {
"type": "string",
"enum": [
"watch",
"discovery"
]
}
},
{
"name": "target",
"in": "query",
"schema": {
"type": "string",
"enum": [
"list",
"market"
]
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Monitors retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Monitor"
}
},
"total": {
"type": "integer",
"description": "Count of monitors in the fetched window — NOT a global total and NOT a paging signal. Because the endpoint fetches each family only to `offset+limit`, `total` cannot exceed the current page's end when a single family is populated (a watch-only org, or any `kind`/`target` filter), so a `while offset < total` loop under-pages. Page by `count` instead. (Same window-count contract as `GET /v1/monitors/{monitorId}/signals`.)"
},
"limit": {
"type": "integer",
"description": "Page size actually applied."
},
"offset": {
"type": "integer",
"description": "Zero-based offset of this page."
},
"count": {
"type": "integer",
"description": "Number of monitors returned in this page — the paging signal: a page shorter than `limit` is the last page."
}
}
}
}
}
},
"422": {
"description": "Validation failed (unknown kind or target).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor",
"description": "Free.",
"operationId": "getMonitor",
"x-mcp-name": "monitors-get",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Monitor retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor",
"description": "Watch monitors update `name`, `schedule`, or `target.listId`;\ndiscovery monitors update `name`, the check's `prompt`/`filters`,\nor `webhook`. A monitor's kind and signal type are immutable —\ncreate a new monitor instead. Free.\n",
"operationId": "updateMonitor",
"x-mcp-name": "monitors-update",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMonitorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Monitor updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation failed (immutable field, wrong-kind fields).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor",
"description": "Deletes a monitor of either kind (previously the watch family had\nno delete). An active watch monitor's schedule is stopped first.\nFree.\n",
"operationId": "deleteMonitor",
"x-mcp-name": "monitors-delete",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Monitor deleted."
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/pause": {
"post": {
"summary": "Pause a monitor",
"description": "One pause/resume vocabulary across both kinds (decision 2). Free.",
"operationId": "pauseMonitor",
"x-mcp-name": "monitors-pause",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/resume": {
"post": {
"summary": "Resume a monitor",
"description": "Free.",
"operationId": "resumeMonitor",
"x-mcp-name": "monitors-resume",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Monitor resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/trigger": {
"post": {
"summary": "Trigger a monitor run",
"description": "Runs the monitor immediately.\n\n**The trigger call itself is free. The run it starts bills per\nemitted signal** under the `monitor-signals` feature (see /pricing\nfor the current rate; per-organization overrides apply):\n\n- a **watch** run charges only for entities whose answer is new or\n changed — unchanged entities and cached answers emit nothing and\n cost nothing;\n- a **discovery** run charges per stored signal.\n\nA trigger against a large list can therefore debit many credits. A\nrun can also stop part-way with `402` if the balance is exhausted\nmid-fan-out, leaving the entities already processed billed and the\nrest not run.\n",
"operationId": "triggerMonitor",
"x-mcp-name": "monitors-trigger",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Run started.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Monitor"
}
}
}
},
"402": {
"description": "Insufficient credits to start (or complete) the run's fan-out.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/signals": {
"get": {
"summary": "List a monitor's signals",
"description": "The occurrences this monitor emitted, newest first, on the signal\nenvelope (`type`, `occurredAt`, `observedAt`, `entity`, `payload`).\nWatch monitors emit `result_changed` when an answer changes —\nsilent no-change runs are runs, not signals, and are not returned\nhere. Enumerating every individual run (no-change and failed runs\nincluded) is not available on the MCP surface; this route returns\nemitted signals only. Reads are free.\n",
"operationId": "listMonitorSignals",
"x-mcp-name": "monitors-list_signals",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MonitorSignal"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "No monitor with this id in your organization.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"post": {
"summary": "Create a monitor action",
"description": "Actions fire when the monitor emits signals. Watch monitors only at\nlaunch — discovery monitors deliver via their webhook and return\n`422` here. Free.\n",
"operationId": "createMonitorAction",
"x-mcp-name": "monitors-create_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"422": {
"description": "Validation failed, or actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List monitor actions",
"description": "Watch monitors only at launch. Free.",
"operationId": "listMonitorActions",
"x-mcp-name": "monitors-list_actions",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Actions retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"422": {
"description": "Actions unsupported for this monitor kind.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}": {
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a monitor action",
"description": "Free.",
"operationId": "getMonitorAction",
"x-mcp-name": "monitors-get_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"200": {
"description": "Action retrieved.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a monitor action",
"description": "Free.",
"operationId": "updateMonitorAction",
"x-mcp-name": "monitors-update_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a monitor action",
"description": "Free.",
"operationId": "deleteMonitorAction",
"x-mcp-name": "monitors-delete_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"responses": {
"204": {
"description": "Action deleted."
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a monitor action",
"description": "Free.",
"operationId": "pauseMonitorAction",
"x-mcp-name": "monitors-pause_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/monitors/{monitorId}/actions/{actionId}/resume": {
"post": {
"summary": "Resume a monitor action",
"description": "The unified verb for the legacy `unpause` (decision 2). Free.",
"operationId": "resumeMonitorAction",
"x-mcp-name": "monitors-resume_action",
"x-docs-group": "Monitors",
"tags": [
"Monitors"
],
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action resumed.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"404": {
"description": "No action with this id on this monitor.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research": {
"post": {
"summary": "Start a contact research job",
"x-mcp-description": "Start asynchronous AI research for a contact using LinkedIn and other sources, then poll the research ID for results.",
"description": "Submit contact details to create a new contact research request. The AI will gather information\nfrom LinkedIn, email services, and other data sources to provide comprehensive contact insights.\n\nThe research will be processed asynchronously. Use the returned contact research ID to check status\nvia the GET endpoint, or configure a webhook to receive completion notifications.\n\n## Credits\n\nEach research request charges **2 credits**. Credits are deducted on success and released if the research fails.\n",
"operationId": "createContactResearch",
"x-credit-feature": "contact-research",
"x-credit-cost": 2,
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-create_research",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactResearchRequest"
},
"examples": {
"basic_request": {
"summary": "Basic contact research request",
"value": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"jobTitle": "VP of Sales"
}
},
"with_linkedin": {
"summary": "Request with LinkedIn URL",
"value": {
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Tech Startup Inc",
"companyDomain": "techstartup.io",
"contactProfileUrl": "https://linkedin.com/in/janesmith",
"webhookUrl": "https://myapp.com/webhooks/contact-research"
}
},
"with_sales_navigator": {
"summary": "Request with Sales Navigator URL",
"value": {
"firstName": "Michael",
"lastName": "Johnson",
"companyName": "Enterprise Corp",
"companyDomain": "enterprise.com",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/lead/ACwAAABcD...",
"jobTitle": "CTO"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact research request created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchResponse"
},
"examples": {
"created_request": {
"summary": "Newly created contact research request",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"createdAt": "2024-01-15T10:30:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed JSON",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validation_error": {
"summary": "Validation error",
"value": {
"error": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "companyDomain",
"message": "must be a valid hostname"
}
]
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "Get contact research by external ID",
"x-mcp-description": "Retrieve contact research using an external system ID, such as a HubSpot contact ID, plus the source name.",
"description": "Retrieve a contact research request by external ID and external source. This endpoint allows you\nto look up contact research requests using identifiers from external systems (e.g., HubSpot contact ID).\n",
"operationId": "getContactResearchByExternalID",
"x-mcp-name": "contacts-get_research_by_external_id",
"x-docs-group": "Contact research & search",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "externalId",
"in": "query",
"required": true,
"description": "The external identifier of the contact research request (e.g., HubSpot contact ID)",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"example": "hubspot_12345"
},
{
"name": "externalSource",
"in": "query",
"required": true,
"description": "The source system that provided the external ID (e.g., \"hubspot\")",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"example": "hubspot"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"linkedInUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA"
}
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"bad_request": {
"summary": "Malformed request",
"value": {
"error": "BAD_REQUEST",
"message": "Invalid JSON in request body"
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_params": {
"summary": "Missing query parameters",
"value": {
"error": "VALIDATION_ERROR",
"message": "Query parameters externalId and externalSource are required"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/research/{id}": {
"get": {
"summary": "Get contact research by ID",
"x-mcp-description": "Retrieve the current status and AI-generated insights for a contact research job when it has completed.",
"description": "Retrieve a contact research request by ID, including its status and results if completed.\nThis endpoint returns the current state of the research request.\n",
"operationId": "getContactResearch",
"x-docs-group": "Contact research & search",
"x-mcp-name": "contacts-get_research",
"tags": [
"Contacts"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the contact research request",
"schema": {
"type": "string"
},
"example": "cr_abc123def456"
}
],
"responses": {
"200": {
"description": "Contact research retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactResearchDetailResponse"
},
"examples": {
"processing_request": {
"summary": "Contact research still processing",
"value": {
"id": "cr_abc123def456",
"status": "processing",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"completed_request": {
"summary": "Completed contact research with insights",
"value": {
"id": "cr_abc123def456",
"status": "completed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"contactProfileUrl": "https://linkedin.com/in/johndoe",
"jobTitle": "VP of Sales",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:00Z",
"profile": {
"summary": "Experienced sales leader with 15+ years in enterprise software",
"currentRole": "VP of Sales",
"currentCompany": "Acme Corp",
"location": "San Francisco, CA",
"experience": [
{
"title": "VP of Sales",
"company": "Acme Corp",
"duration": "2020 - Present"
}
]
},
"communicationStyle": {
"preference": "professional and direct"
},
"interests": [
"Enterprise SaaS",
"Sales enablement"
],
"challenges": [
"Scaling sales team",
"Entering new markets"
],
"linkedInActivity": {
"postingFrequency": "Weekly",
"engagementLevel": "High",
"lastActivityDate": "2024-01-10T14:30:00Z"
}
}
},
"failed_request": {
"summary": "Failed contact research with error",
"value": {
"id": "cr_abc123def456",
"status": "failed",
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Corp",
"companyDomain": "acme.com",
"error": "Could not retrieve LinkedIn profile data",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:32:00Z"
}
}
}
}
}
},
"404": {
"description": "Not Found - Contact research not found or access denied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"not_found": {
"summary": "Contact research not found",
"value": {
"error": "NOT_FOUND",
"message": "Contact research not found or access denied"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/search": {
"post": {
"summary": "Search contacts with LinkedIn Sales Navigator",
"x-mcp-description": "Search for contacts by person, role, tenure, industry, company headcount, company type, or recent job change using the API key owner's Sales Navigator connection.",
"description": "Search for contacts using LinkedIn Sales Navigator. Optionally scoped to one or more companies\nvia companyLinkedInUrls. When multiple URLs are provided, each company is searched separately\nand results are merged and deduplicated.\n\n**Pagination:** each request returns exactly one page (default size 25, max 100 via `limit`);\nthere is no server-side auto-fetch. Pass the previous response's `nextCursor` as `cursor` to\nfetch the next page, together with the same filters and `limit`. The cursor keeps every page\nin one pagination session on the same Sales Navigator connector. If that connector becomes\nunavailable, the request returns `422 PAGINATION_SOURCE_UNAVAILABLE`; restart the search\nwithout a cursor. A `429` is temporary: retry the same cursor later. `offset` remains accepted\nfor existing callers, but it does not keep connector continuity; provide cursor or offset, not both.\nAn invalid, expired, or older cursor returns `422 INVALID_CURSOR`; restart without a cursor.\nPaging is capped at a 1000-result-per-search ceiling: a page STARTING at or past it is\nrejected with `422`, a page that merely runs past it is clamped to the remaining rows, and\n`nextCursor` is omitted once the next page would start at the ceiling; narrow the search\nfilters instead of paging deeper.\n\n**Requirements:**\n- At least one search parameter must be provided: `companyLinkedInUrls`, `firstName`, `lastName`, `jobTitles`, `keywords`, `countries`, `departments`, `seniorityLevels`, `recentlyChangedJobs`, `yearsInCurrentPosition`, `yearsAtCurrentCompany`, `industries`, `companyHeadcounts`, or `companyTypes`.\n- LinkedIn Sales Navigator connection is required for the API key owner\n\n**Response:**\n- If Sales Navigator is not connected, returns `salesNavConnected: false` with empty contacts\n- If connected, returns matching contacts with profile information and pagination metadata\n\n## Credits\n\nEach page request charges **0.5 credits** when a connected search\nserves a page (SEL-3685). Not-connected responses\n(`salesNavConnected: false`) and failed requests are free.\n",
"operationId": "searchContacts",
"x-docs-group": "Contact research & search",
"x-credit-feature": "contact-search",
"x-credit-cost": 0.5,
"x-mcp-name": "contacts-search",
"tags": [
"Contacts"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchRequest"
},
"examples": {
"job_title_search": {
"summary": "Search by job titles at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US"
]
}
},
"multi_company_search": {
"summary": "Search across multiple companies",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/google",
"https://linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager"
]
}
},
"name_search": {
"summary": "Search by name (no company scope)",
"value": {
"firstName": "John",
"lastName": "Doe"
}
},
"keyword_search": {
"summary": "Search by keywords at a company",
"value": {
"companyLinkedInUrls": [
"https://linkedin.com/company/techstartup"
],
"keywords": "Kubernetes, DevOps",
"jobTitles": [
"Site Reliability Engineer"
]
}
},
"recent_movers": {
"summary": "Search recent movers in a market segment",
"value": {
"recentlyChangedJobs": true,
"yearsInCurrentPosition": [
"Less than 1 year"
],
"industries": [
"Software Development"
],
"companyHeadcounts": [
"51-200",
"201-500"
],
"seniorityLevels": [
"Director",
"Vice President"
]
}
},
"next_page": {
"summary": "Fetch the next page with the same search and limit",
"value": {
"jobTitles": [
"Engineering Manager"
],
"countries": [
"US"
],
"limit": 25,
"cursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Contact search completed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactSearchResponse"
},
"examples": {
"contacts_found": {
"summary": "Role-change result with two current positions",
"value": {
"items": [
{
"firstName": "Alex",
"lastName": "Rivera",
"fullName": "Alex Rivera",
"role": "Vice President of Revenue",
"companyName": "Acme Software",
"seniority": [
"VP"
],
"positions": [
{
"companyName": "Open Technology Foundation",
"title": "Board Advisor",
"startedOn": {
"year": 2022,
"month": 1
},
"tenureAtPosition": {
"years": 4,
"months": 8
},
"tenureAtCompany": {
"years": 4,
"months": 8
},
"moveType": "no_recent_move"
},
{
"companyName": "Acme Software",
"title": "Vice President of Revenue",
"startedOn": {
"year": 2026,
"month": 6
},
"tenureAtPosition": {
"months": 3
},
"tenureAtCompany": {
"years": 4,
"months": 3
},
"moveType": "moved_internally"
}
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123",
"avatar": "https://media.licdn.com/...",
"location": "San Francisco, California"
}
],
"total": 150,
"limit": 25,
"offset": 0,
"hasMore": true,
"salesNavConnected": true,
"nextCursor": "djI6MjU6Y29ubmVjdG9yLTEyMw"
}
},
"sales_nav_not_connected": {
"summary": "Sales Navigator not connected",
"value": {
"items": [],
"total": 0,
"limit": 25,
"offset": 0,
"hasMore": false,
"salesNavConnected": false
}
}
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body (invalid JSON)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"malformed_json": {
"summary": "Malformed JSON",
"value": {
"error": {
"type": "BAD_REQUEST",
"code": "BAD_REQUEST",
"message": "Invalid request body",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid department or seniority value, or a company URL that cannot be resolved). Also returned with error code `PAGE_BEYOND_CEILING` when `offset` (or a replayed `cursor`) starts at or past the 1000-result search ceiling. A continuation page also returns `PAGINATION_SOURCE_UNAVAILABLE` if its original Sales Navigator connector is no longer available, or if an older service version cannot preserve connector affinity. A search with `departments` returns `CONTACT_SEARCH_FILTER_DROPPED` if LinkedIn says it did not apply the department filter. This problem can be temporary, so retry the search. An invalid, expired, or older cursor returns `INVALID_CURSOR`. Branch on the code, not the status.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"page_beyond_ceiling": {
"summary": "Page starts at or past the 1000-result ceiling",
"value": {
"error": {
"type": "VALIDATION",
"code": "PAGE_BEYOND_CEILING",
"message": "offset starts past the 1000-result search ceiling; narrow the search filters instead",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_department": {
"summary": "Department value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'departments': invalid value: Engineering Ops",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"department_filter_dropped": {
"summary": "LinkedIn did not apply the requested department filter",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "LinkedIn did not apply the requested departments filter",
"errorCode": "CONTACT_SEARCH_FILTER_DROPPED",
"errorAction": "Retry the search up to 2 times. If it still fails, stop retrying for this company and department. Remove departments only if results from all departments are acceptable.",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_seniority": {
"summary": "Seniority value not in the LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'seniorityLevels': invalid value: VP",
"requestId": "0c0acb3e-b653-4057-9f13-45157cf7a18f"
}
}
},
"invalid_industry": {
"summary": "Industry value not in the current LSN taxonomy",
"value": {
"error": {
"type": "VALIDATION",
"code": "VALIDATION_ERROR",
"message": "validation failed for 'industries': invalid value: \"Computer Software\"; did you mean \"Software Development\"?\nallowed values: https://docs.saber.app/contacts/industry-values",
"requestId": "07fd1dca-4b52-4c52-9de2-c36ca9264759"
}
}
},
"pagination_source_unavailable": {
"summary": "The pagination session's Sales Navigator connector is unavailable",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "identity_invalid"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"pagination_connector_affinity_unsupported": {
"summary": "An older service version cannot preserve connector affinity",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "The Sales Navigator connector used for this pagination session is unavailable.",
"errorCode": "PAGINATION_SOURCE_UNAVAILABLE",
"errorAction": "Restart the search without a cursor.",
"details": {
"connector": "salesNavigator",
"reason": "connector_affinity_unsupported"
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
},
"invalid_cursor": {
"summary": "The cursor is invalid, expired, or from an older format",
"value": {
"error": {
"type": "VALIDATION",
"code": "INVALID_CURSOR",
"message": "invalid or expired cursor; restart the search from page 1",
"requestId": "a8989694-ed6f-4676-a8a3-cc3084485564"
}
}
},
"invalid_linkedin_url": {
"summary": "Company LinkedIn URL could not be parsed",
"value": {
"error": {
"type": "UNPROCESSABLE_ENTITY",
"code": "UNPROCESSABLE_ENTITY",
"message": "Failed to parse company LinkedIn URL. Please ensure the URL is valid.",
"requestId": "deaf32c8-9680-4fa3-b93d-9f017ee8c4dc"
}
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - The API key or the pagination session's Sales Navigator connector is temporarily rate-limited. Retry the same cursor later.\n",
"headers": {
"Retry-After": {
"description": "Seconds to wait before retrying.",
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/RateLimitErrorResponse"
},
{
"$ref": "#/components/schemas/ErrorResponse"
}
]
},
"examples": {
"rate_limited": {
"summary": "Rate limit exceeded",
"value": {
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"retryAfter": 45
}
},
"pagination_connector_rate_limited": {
"summary": "The connector for this pagination session is rate-limited",
"value": {
"error": {
"type": "TOO_MANY_REQUESTS",
"code": "TOO_MANY_REQUESTS",
"message": "LinkedIn rate limit exceeded. Please retry later.",
"errorCode": "LINKEDIN_RATE_LIMIT_EXCEEDED",
"details": {
"connector": "salesNavigator",
"reason": "rate_limited",
"retry_after_seconds": 300
},
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
},
"500": {
"description": "Internal Server Error - Includes the unexpected `PAGINATION_CONNECTOR_AFFINITY_INVALID` case, where the service reports that a different connector served the continuation page. Restart without a cursor. Contact support if the error repeats.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"pagination_connector_affinity_invalid": {
"summary": "A different connector served the continuation page",
"value": {
"error": {
"type": "INTERNAL",
"code": "PAGINATION_CONNECTOR_AFFINITY_INVALID",
"message": "Contact search could not verify its pagination connector",
"requestId": "e69ed773-2674-4dca-be61-886d6698f360"
}
}
}
}
}
}
}
}
}
},
"/v1/contacts/lists": {
"post": {
"summary": "Create a contact list",
"x-mcp-description": "Run a LinkedIn Sales Navigator search, store a point-in-time contact snapshot, and return contact list metadata.",
"description": "Creates a named contact list by running a live LinkedIn Sales Navigator search for the given company and filters, then storing the results as a persistent snapshot.\n\nThe request is synchronous — it waits for the search to complete before returning. The response includes the list metadata with an accurate `contactCount`.\n\n**Requirements:**\n- A valid company LinkedIn URL is required\n- At least one filter (`jobTitles`, `keywords`, or `countries`) must be provided\n- LinkedIn Sales Navigator must be connected on the API key owner's account\n\n**Notes:**\n- Up to ~125 contacts are captured (5 pages × 25 results per page)\n- Contacts are stored as a point-in-time snapshot; the list is not automatically updated when profiles change\n- The list and all its contacts are committed atomically — if the request fails, no partial data is stored\n",
"operationId": "createContactList",
"x-mcp-name": "contact_lists-create",
"tags": [
"Contact Lists"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateContactListRequest"
},
"examples": {
"single_company": {
"summary": "Engineering leads at one company",
"value": {
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
}
}
},
"multi_company": {
"summary": "Search across multiple companies",
"value": {
"name": "Kubernetes engineers at Google and Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/google",
"https://www.linkedin.com/company/stripe"
],
"keywords": "Kubernetes",
"jobTitles": [
"Site Reliability Engineer"
]
}
}
},
"cross_company": {
"summary": "Cross-company list (no company scope)",
"value": {
"name": "VPs of Engineering in the US",
"filters": {
"jobTitles": [
"VP Engineering",
"VP of Engineering"
],
"countries": [
"US"
]
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Contact list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
},
"examples": {
"created_with_contacts": {
"summary": "List created with contacts",
"value": {
"id": "abc123",
"name": "Engineering leads at Stripe",
"filters": {
"companyLinkedInUrls": [
"https://www.linkedin.com/company/stripe"
],
"jobTitles": [
"Engineering Manager",
"VP Engineering"
],
"countries": [
"US",
"GB"
]
},
"contactCount": 47,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
},
"created_no_contacts": {
"summary": "List created, no matching contacts found",
"value": {
"id": "def456",
"name": "Very specific search",
"filters": {
"jobTitles": [
"Chief Blockchain Officer"
],
"countries": [
"LI"
]
},
"contactCount": 0,
"createdAt": "2026-03-03T12:00:00Z",
"updatedAt": "2026-03-03T12:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request — missing required filter or invalid URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"missing_filters": {
"summary": "No filters provided",
"value": {
"error": "BAD_REQUEST",
"message": "At least one filter (jobTitles, keywords, or countries) is required"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"504": {
"description": "Gateway Timeout — Sales Navigator search exceeded the request timeout",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List contact lists",
"x-mcp-description": "List saved contact lists with pagination.",
"operationId": "listContactLists",
"x-mcp-name": "contact_lists-list",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contact lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}": {
"get": {
"summary": "Get a contact list by ID",
"x-mcp-description": "Retrieve contact list metadata and the stored contact count.",
"operationId": "getContactList",
"x-mcp-name": "contact_lists-get",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"200": {
"description": "Contact list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Rename a contact list",
"x-mcp-description": "Rename a saved contact list without changing its stored contact snapshot.",
"operationId": "updateContactList",
"x-mcp-name": "contact_lists-update",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateContactListRequest"
},
"example": {
"name": "Senior engineers at Stripe"
}
}
}
},
"responses": {
"200": {
"description": "Contact list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactList"
}
}
}
},
"400": {
"description": "Bad Request — invalid name",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a contact list and all its stored contacts",
"x-mcp-description": "Permanently delete a contact list and its stored contact snapshots.",
"description": "Permanently deletes the contact list and all its stored contact snapshots.\n",
"operationId": "deleteContactList",
"x-mcp-name": "contact_lists-delete",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
}
],
"responses": {
"204": {
"description": "Contact list deleted successfully"
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/contacts/lists/{listId}/contacts": {
"get": {
"summary": "Get contacts in a contact list",
"x-mcp-description": "Page through contacts stored in a saved list without making a new Sales Navigator request.",
"description": "Returns the stored contact snapshot for the given list, paginated. Reads directly from the database — no Sales Navigator API call is made, so this is always fast and does not consume credits.\n\nContacts are returned in the order they were stored at list creation time.\n",
"operationId": "getContactListContacts",
"x-mcp-name": "contact_lists-get_contacts",
"tags": [
"Contact Lists"
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the contact list",
"schema": {
"type": "string"
},
"example": "abc123"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of contacts to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of contacts to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Contacts retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactListContactsResponse"
},
"examples": {
"contacts": {
"summary": "First page of contacts",
"value": {
"items": [
{
"id": "item_001",
"listId": "abc123",
"baseContactId": "bc_001",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"role": "Engineering Manager",
"companyName": "Stripe",
"location": "San Francisco, California",
"seniority": [
"MANAGER"
],
"linkedInSalesNavigatorProfileUrl": "https://www.linkedin.com/sales/lead/ACwAAABOW1g",
"createdAt": "2026-03-03T12:00:00Z"
}
],
"total": 47,
"limit": 25,
"offset": 0,
"hasMore": true
}
}
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Contact list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates": {
"post": {
"summary": "Create a signal template",
"x-mcp-description": "Create a reusable research question template that can be applied to many companies in batch.",
"description": "Create a reusable signal template that can be referenced when creating signals.\nTemplates allow you to define standard research questions that can be applied to multiple companies.\n",
"operationId": "createSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-create",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalTemplateRequest"
}
}
}
},
"responses": {
"201": {
"description": "Template created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all signal templates for your organization",
"description": "Retrieve a paginated list of signal templates for your organization.\nReturns the latest active version of each template.\n",
"operationId": "listSignalTemplates",
"deprecated": true,
"x-mcp-name": "signal_templates-list",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of templates to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of templates to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include deleted templates in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Templates retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalTemplatesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/templates/{templateId}": {
"get": {
"summary": "Get a signal template by ID",
"description": "Retrieve a specific signal template by ID",
"operationId": "getSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-get",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Template retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a signal template",
"x-mcp-description": "Update a signal template by creating a new version while preserving the template ID. Omitted fields keep their previous values.",
"description": "Update a signal template using PATCH semantics (partial update). This creates a new version of the template\nand soft-deletes the previous version. Only provided fields will be updated; omitted fields will retain their\nvalues from the previous version.\n\nThe template ID remains the same across all versions. The version number will be incremented.\n",
"operationId": "updateSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-update",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalTemplateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Template updated successfully (new version created)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a signal template",
"x-mcp-description": "Soft-delete a signal template so it is no longer active while preserving it for historical tracking.",
"description": "Soft-delete a signal template. The template will be marked as deleted but preserved for historical tracking.",
"operationId": "deleteSignalTemplate",
"deprecated": true,
"x-mcp-name": "signal_templates-delete",
"x-mcp-hidden": true,
"tags": [
"Signals Templates"
],
"parameters": [
{
"name": "templateId",
"in": "path",
"required": true,
"description": "The unique identifier of the template to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Template deleted successfully (no content returned)"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Template not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions": {
"get": {
"summary": "List all signal subscriptions for your organization",
"description": "Retrieve a paginated list of signal subscriptions. Each subscription is merged\nwith its signal template data so the response includes the full template definition\nalongside the schedule configuration.\n",
"operationId": "listSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-list",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSignalSubscriptionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"summary": "Create a signal subscription",
"x-mcp-description": "Schedule recurring signal execution for a template, domain, or list target.",
"description": "Create a subscription that schedules recurring signal executions.\n\nYou can either reference an existing template via `signalTemplateId`, or create a\ntemplate inline by providing `name` and `question` (plus optional fields like\n`answerType`, `description`, `weight`, `qualificationCriteria`, `outputSchema`).\n\nThe subscription is created in a stopped state. Use the schedule lifecycle\nendpoints (`POST .../start`) to activate it.\n\nCreating and managing subscriptions is free. Each scheduled or triggered run generates signals, and every freshly generated signal charges **2 credits** — the same pricing and caching rules as `POST /v1/companies/signals`.\n",
"operationId": "createSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-create",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a signal subscription by ID",
"description": "Retrieve a single subscription merged with its signal template data.",
"operationId": "getSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-get",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a signal subscription",
"description": "Update a subscription's schedule configuration and/or its underlying template fields.\nWhen schedule fields are provided, the active schedule is updated.\nWhen template fields are provided, the underlying signal template is updated inline.\n",
"operationId": "updateSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-update",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSignalSubscriptionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/start": {
"post": {
"summary": "Start a signal subscription",
"description": "Activate a stopped subscription. Begins recurring signal execution\naccording to the subscription's cron expression.\n",
"operationId": "startSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-start",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to start",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription started successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/stop": {
"post": {
"summary": "Stop a signal subscription",
"description": "Pause an active subscription so no further recurring executions occur.\nThe subscription can be restarted later.\n",
"operationId": "stopSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-stop",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to stop",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription stopped successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a signal subscription",
"description": "Trigger an immediate execution of the subscription regardless of the cron schedule.\nWorks for both active and stopped subscriptions.\n\nThe trigger itself is free. Every signal the run freshly generates charges **2 credits** (same pricing and caching rules as `POST /v1/companies/signals`); results served from cache are free.\n",
"operationId": "triggerSignalSubscription",
"deprecated": true,
"x-mcp-name": "signal_subscriptions-trigger",
"x-mcp-hidden": true,
"tags": [
"Signal Subscriptions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions": {
"post": {
"summary": "Create a subscription action",
"description": "Create an action on a subscription. Actions define what happens when the\nsubscription triggers (e.g., deliver results via webhook).\nA maximum of 10 actions per subscription is allowed.\n",
"operationId": "createSubscriptionAction",
"x-mcp-name": "subscription_actions-create",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription to add the action to",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubscriptionActionRequest"
}
}
}
},
"responses": {
"201": {
"description": "Action created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all actions for a subscription",
"description": "Retrieve a paginated list of actions configured on a subscription.",
"operationId": "listSubscriptionActions",
"x-mcp-name": "subscription_actions-list",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription whose actions to list",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of actions to return",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of actions to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Actions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSubscriptionActionsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}": {
"get": {
"summary": "Get a subscription action by ID",
"description": "Retrieve a single action by its ID.",
"operationId": "getSubscriptionAction",
"x-mcp-name": "subscription_actions-get",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a subscription action",
"description": "Replace the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be updated.\n",
"operationId": "updateSubscriptionAction",
"x-mcp-name": "subscription_actions-update",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSubscriptionActionRequest"
}
}
}
},
"responses": {
"200": {
"description": "Action updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"400": {
"description": "Invalid action configuration",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a subscription action",
"description": "Permanently delete an action from a subscription.",
"operationId": "deleteSubscriptionAction",
"x-mcp-name": "subscription_actions-delete",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "Action deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/pause": {
"post": {
"summary": "Pause a subscription action",
"description": "Disable an action so it is skipped during dispatch. The action\nconfiguration is preserved and can be re-enabled with unpause.\n",
"operationId": "pauseSubscriptionAction",
"x-mcp-name": "subscription_actions-pause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to pause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/subscriptions/{subscriptionId}/actions/{actionId}/unpause": {
"post": {
"summary": "Unpause a subscription action",
"description": "Re-enable a paused action so it resumes being dispatched when the\nsubscription triggers.\n",
"operationId": "unpauseSubscriptionAction",
"x-mcp-name": "subscription_actions-unpause",
"x-mcp-hidden": true,
"tags": [
"Subscription Actions"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The subscription that owns the action",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "actionId",
"in": "path",
"required": true,
"description": "The action ID to unpause",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Action unpaused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription or action not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/signals/summaries": {
"post": {
"summary": "Generate an AI summary consolidating insights from all completed company signals for a domain",
"description": "Generate an AI-powered summary of all completed signals for a given domain. The summary\nconsolidates key insights from multiple signals into structured data points.\n\nIf there are no completed signals for the domain, returns 204 No Content.\nIf a summary is already in progress for this domain, returns 409 Conflict.\nIf there are no new signals since the last summary, returns the latest existing summary.\n\nGenerating a summary consumes no credits — it consolidates signals you have already paid for.\n",
"operationId": "generateSignalSummary",
"deprecated": true,
"x-mcp-name": "signal_summaries-generate",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenerateSummaryRequest"
},
"examples": {
"basic_request": {
"summary": "Generate summary for a domain",
"value": {
"domain": "acme.com"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Summary generated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SummaryResponse"
},
"examples": {
"summary_generated": {
"summary": "Generated summary with data points",
"value": {
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
},
{
"description": "Company uses AWS for cloud infrastructure",
"referenceQuestions": [
"What cloud providers does this company use?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/technology",
"title": "Technology Stack",
"snippet": "Built on AWS cloud infrastructure..."
}
]
}
]
}
}
}
}
}
},
"204": {
"description": "No Content - No completed signals available for summary"
},
"400": {
"description": "Bad Request - Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Conflict - A summary is already in progress for this domain",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "A summary for this domain is already in progress"
}
}
},
"example": {
"message": "A summary for this domain is already in progress"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all AI-generated signal summaries for a domain, ordered by creation date (latest first)",
"description": "Retrieve all summaries that have been generated for a given domain with pagination support.\nReturns summaries ordered by creation date (latest first).\n",
"operationId": "getSignalSummaries",
"deprecated": true,
"x-mcp-name": "signal_summaries-list",
"x-mcp-hidden": true,
"tags": [
"Signals Summaries"
],
"parameters": [
{
"name": "domain",
"in": "query",
"required": true,
"description": "Filter summaries by company domain (e.g., \"acme.com\")",
"schema": {
"type": "string",
"pattern": "^[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])?)*$"
},
"example": "acme.com"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of results per page",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"example": 20
},
{
"name": "offset",
"in": "query",
"description": "Number of results to skip for pagination",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
}
],
"responses": {
"200": {
"description": "Summaries retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"count": {
"type": "integer",
"description": "Number of results in this response",
"example": 5
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"examples": {
"summaries_list": {
"summary": "List of summaries for a domain",
"value": {
"results": [
{
"id": "58f74458-7e71-4ab6-8297-55dab9c752c0",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp",
"snippet": "Leading provider of enterprise software..."
}
]
}
],
"status": "completed",
"signalsCount": 10,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:32:00Z"
},
{
"id": "7e53d8a2-6a4b-4c66-aea3-40d29462e433",
"summary": [
{
"description": "Company operates in the Enterprise Software industry",
"referenceQuestions": [
"What industry does this company operate in?"
],
"qualification": "good",
"sources": [
{
"url": "https://acme.com/about",
"title": "About Acme Corp"
}
]
}
],
"status": "completed",
"signalsCount": 9,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:02:00Z"
}
],
"total": 2,
"limit": 20,
"offset": 0,
"count": 2
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions": {
"post": {
"summary": "Create a market signal subscription to monitor job posts, LinkedIn posts, fundraising, investments, or IPOs",
"description": "Create a subscription that continuously monitors external data sources and delivers matching signals\nto your webhook. Subscriptions run on a configurable polling interval (daily or weekly) and\nautomatically detect and deliver new signals as they appear.\n\n---\n\n## Subscription Types\n\n| Type | Description | Data Source |\n|---|---|---|\n| `JOB_POSTS` | Monitor new job postings matching your criteria | Job posting aggregators |\n| `LINKEDIN_POST` | Monitor LinkedIn posts matching keyword filters | LinkedIn (requires Sales Navigator) |\n| `FUND_RAISED` | Monitor fund closings and fundraising events | Web news search + RSS feeds + SEC EDGAR (Form D) |\n| `RECENT_INVESTMENT` | Monitor recent investment and funding rounds | Web news search + RSS feeds |\n| `IPO` | Monitor IPO and public listing announcements | Web news search + RSS feeds. SEC EDGAR registration filings are **not** guaranteed on this type — use `IPO_LIFECYCLE` for filing-derived coverage. |\n| `IPO_LIFECYCLE` | Monitor deterministic US IPO lifecycle events (filing, amendment, pricing, prospectus, listing, Form 5500) | SEC EDGAR getcurrent + newswire pricing and listing releases + DOL EFAST |\n| `GITHUB_REPO_ACTIVITY` | Monitor issues and pull requests opened on repositories and organizations you watch, resolved to the author's employer | GitHub, on your own connector's token |\n\n---\n\n## Filter Modes\n\nFilters can be provided in three ways:\n\n1. **Prompt only** (JOB_POSTS): Provide a natural language `prompt` and the AI generates complete filters\n2. **Prompt + filters** (JOB_POSTS): Provide both; the AI augments your filters with prompt intent\n3. **Filters only**: Provide structured filters directly (required for LINKEDIN_POST)\n\n---\n\n## Webhook Delivery\n\nMatched signals are batched and delivered to your `webhookUrl` on each polling interval.\nEach delivery includes an array of signal objects with metadata, payload, and confidence scores.\nUse `webhookSecret` to verify webhook signatures via HMAC-SHA256.\n\n---\n\n## LinkedIn Post Requirements\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account.\nIf no connector is found, the request returns an error with setup instructions.\n\n---\n\n## Credits\n\nCreating and managing subscriptions is free. Each signal **created** by a polling run charges **8 credits**, at the moment the signal is stored — webhook delivery happens afterwards and does not affect the charge. Runs that create no signals are free. Use `intervalSignalLimit` to cap how many signals — and therefore how many credits — a single polling cycle can consume.\n",
"operationId": "createMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-create_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMarketSignalSubscriptionRequest"
},
"examples": {
"job_posts_with_filters": {
"summary": "Monitor DevOps job postings in the US",
"value": {
"type": "JOB_POSTS",
"name": "DevOps hiring in US tech",
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Site Reliability Engineer"
],
"countries": [
"US"
],
"minEmployees": 50,
"maxEmployees": 500,
"excludeRecruitingAgencies": true
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily",
"intervalSignalLimit": 100
}
},
"job_posts_with_prompt": {
"summary": "Generate filters from natural language prompt",
"value": {
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"linkedin_post": {
"summary": "Monitor LinkedIn posts about AI",
"value": {
"type": "LINKEDIN_POST",
"name": "AI thought leadership posts",
"filters": {
"keywordsAll": [
"artificial intelligence"
],
"keywordsAny": [
"GPT",
"LLM",
"generative AI"
],
"keywordsNone": [
"course",
"tutorial"
],
"promptFilter": "Only include posts from C-level executives or VPs at technology companies discussing enterprise AI adoption"
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"fund_raised": {
"summary": "Monitor fund closings",
"value": {
"type": "FUND_RAISED",
"name": "PE fund closings",
"filters": {
"keywords": [
"private equity",
"growth fund"
],
"excludeKeywords": [
"crypto",
"blockchain"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"recent_investment": {
"summary": "Monitor Series B+ funding rounds",
"value": {
"type": "RECENT_INVESTMENT",
"name": "Late-stage funding rounds",
"filters": {
"keywords": [
"Series B",
"Series C",
"growth round"
],
"maxLookbackDays": 14
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "daily"
}
},
"ipo": {
"summary": "Monitor IPO announcements",
"value": {
"type": "IPO",
"name": "Tech IPOs",
"filters": {
"keywords": [
"technology",
"SaaS",
"software"
],
"maxLookbackDays": 30
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"interval": "weekly"
}
},
"ipo_lifecycle": {
"summary": "Monitor US IPO lifecycle events",
"value": {
"type": "IPO_LIFECYCLE",
"name": "US IPO lifecycle tracker",
"filters": {
"eventTypes": [
"filing",
"amendment",
"pricing",
"prospectus",
"listing",
"form5500"
],
"formTypes": [
"S-1",
"F-1",
"S-11",
"N-2"
],
"countries": [
"US"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"webhookSecret": "whsec_abc123",
"interval": "daily"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Subscription created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
},
"examples": {
"created": {
"summary": "Subscription created with generated filters",
"value": {
"id": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"organizationId": "org_123",
"type": "JOB_POSTS",
"name": "AI/ML hiring startups",
"status": "active",
"prompt": "Find companies hiring machine learning engineers at seed to Series B startups in Europe",
"filters": {
"type": "JOB_POSTS",
"titleKeywords": [
"Machine Learning Engineer",
"ML Engineer",
"AI Engineer"
],
"countries": [
"DE",
"FR",
"GB",
"NL",
"SE"
],
"fundingStages": [
"seed",
"series_a",
"series_b"
]
},
"webhookUrl": "https://myapp.com/webhooks/market-signals",
"intervalSignalLimit": 500,
"interval": "daily",
"createdAt": "2024-06-15T10:00:00Z",
"updatedAt": "2024-06-15T10:00:00Z"
}
}
}
}
}
},
"400": {
"description": "Bad Request - Invalid input or missing required fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., LinkedIn connector required for LINKEDIN_POST)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"linkedin_not_connected": {
"summary": "LinkedIn connector not found",
"value": {
"error": "LINKEDIN_CONNECTOR_REQUIRED",
"message": "No LinkedIn / Sales Navigator connector found for this organization. Please connect using the Saber Chrome extension."
}
}
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List all market signal subscriptions for your organization",
"description": "Retrieve a paginated list of market signal subscriptions. By default, deleted subscriptions\nare excluded. Set `includeDeleted=true` to include them.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptions",
"deprecated": true,
"x-mcp-name": "market_signals-list_subscriptions",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of subscriptions to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of subscriptions to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
},
{
"name": "includeDeleted",
"in": "query",
"description": "Include soft-deleted subscriptions in the response",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "Subscriptions retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}": {
"get": {
"summary": "Get a market signal subscription by ID",
"description": "Retrieve a single market signal subscription including its filter configuration and status.",
"operationId": "getMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-get_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"patch": {
"summary": "Update a market signal subscription",
"description": "Partially update a subscription's configuration. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed after creation.\n\nWhen filters or webhook configuration changes, the active polling schedule is\nautomatically refreshed. When the polling interval changes, the schedule is recreated.\n",
"operationId": "updateMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-update_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to update",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMarketSignalSubscriptionRequest"
},
"examples": {
"update_filters": {
"summary": "Update subscription filters",
"value": {
"filters": {
"titleKeywords": [
"DevOps Engineer",
"Platform Engineer"
],
"countries": [
"US",
"GB"
]
}
}
},
"update_interval": {
"summary": "Change polling interval to weekly",
"value": {
"interval": "weekly",
"intervalSignalLimit": 200
}
},
"update_webhook": {
"summary": "Update webhook URL",
"value": {
"webhookUrl": "https://myapp.com/webhooks/v2/market-signals"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Subscription updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"400": {
"description": "Bad Request - Malformed request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error (e.g., invalid webhookUrl or out-of-range intervalSignalLimit)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a market signal subscription",
"description": "Soft-delete a subscription and stop signal delivery. The subscription will no longer\ndeliver signals. This operation cannot be undone via the API.\n",
"operationId": "deleteMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-delete_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to delete",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"204": {
"description": "Subscription deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/pause": {
"post": {
"summary": "Pause a market signal subscription",
"description": "Pause an active subscription. No new signals will be delivered until the\nsubscription is resumed. The subscription configuration is preserved.\n",
"operationId": "pauseMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-pause_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to pause",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription paused successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or already paused/deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/resume": {
"post": {
"summary": "Resume a paused market signal subscription",
"description": "Resume a paused subscription. Signal polling restarts and signals will\nbe delivered again on the next interval.\n",
"operationId": "resumeMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-resume_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to resume",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription resumed successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found or not paused",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/trigger": {
"post": {
"summary": "Trigger an immediate run of a market signal subscription",
"description": "Trigger an immediate polling run for the subscription, regardless of the normal\ninterval schedule. This is useful for testing a new subscription or forcing a\nrefresh after updating filters.\n\nThe request succeeds regardless of the subscription's status. However, only\nsubscriptions in `active` status will poll for new signals and deliver\nsignals. Paused or deleted subscriptions return `200` but do not trigger a run.\n\nThe trigger itself is free; each signal created (stored) by the run charges **8 credits**, independent of webhook delivery (see `POST /v1/market-signals/subscriptions`).\n",
"operationId": "triggerMarketSignalSubscription",
"deprecated": true,
"x-mcp-name": "market_signals-trigger_subscription",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription to trigger",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
],
"responses": {
"200": {
"description": "Subscription triggered successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/market-signals/subscriptions/{subscriptionId}/signals": {
"get": {
"summary": "List signals delivered by a market signal subscription",
"description": "Retrieve a paginated list of signals that have been matched and stored by a subscription.\nEach signal includes the raw payload from the data source (e.g., job posting details,\nLinkedIn post content, or funding announcement), along with metadata like confidence scores\nand delivery status.\n\nResults are sorted by creation date (latest first).\n",
"operationId": "listMarketSignalSubscriptionSignals",
"deprecated": true,
"x-mcp-name": "market_signals-list_signals",
"x-mcp-hidden": true,
"tags": [
"Market Signals"
],
"parameters": [
{
"name": "subscriptionId",
"in": "path",
"required": true,
"description": "The unique identifier of the subscription",
"schema": {
"type": "string",
"format": "uuid"
},
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of signals to return (1-100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of signals to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Signals retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarketSignalListResponse"
},
"examples": {
"signals_list": {
"summary": "List of market signals for a subscription",
"value": {
"items": [
{
"id": "sig_001",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": "jp_123",
"externalSignalId": null,
"payload": {
"title": "Senior DevOps Engineer",
"company": "Acme Corp",
"location": "San Francisco, CA",
"url": "https://jobs.acme.com/devops"
},
"confidenceScore": 0.92,
"status": "delivered",
"publishedAt": "2024-06-14T08:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": "2024-06-15T10:31:00Z"
},
{
"id": "sig_002",
"subscriptionId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"jobPostingId": null,
"externalSignalId": "li_post_456",
"payload": {
"text": "Excited to announce our Series B funding...",
"author": {
"name": "Jane CEO",
"headline": "CEO at TechStartup"
}
},
"confidenceScore": 0.87,
"status": "pending",
"publishedAt": "2024-06-15T09:00:00Z",
"createdAt": "2024-06-15T10:30:00Z",
"deliveredAt": null
}
],
"total": 42,
"limit": 20,
"offset": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Subscription not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/connectors": {
"get": {
"summary": "List connectors and their connection status",
"description": "Returns a list of all integrations (connectors) configured for your organization, along with\ntheir current connection status. Use this endpoint to check which data sources are connected\nto Saber and whether they are actively syncing.\n\n## Supported Connectors\n\n| Source | Description |\n|---|---|\n| `salesNavigator` | LinkedIn Sales Navigator — used for contact search and profile enrichment |\n| `hubspotApp` | HubSpot App — OAuth-based HubSpot marketplace app installation |\n\n## Connection Statuses\n\n| Status | Meaning |\n|---|---|\n| `connected` | The connector is authenticated and operational |\n| `disconnected` | The connector has been manually disconnected |\n| `needs-authentication` | The connector's credentials have expired and require re-authentication |\n| `ready-to-connect` | The connector is configured but not yet connected |\n| `error` | An error occurred with the connector |\n\n> **Note:** `hubspotApp` connection state is inferred from the presence of an active\n> installation record. As a result, it can only be reported as `connected` or omitted\n> entirely — finer-grained statuses such as `needs-authentication` or `error` are not\n> currently available for this connector.\n\n## Common Use Cases\n\n- **Pre-flight checks** — verify that the required connectors are connected before running a workflow\n- **Monitoring** — detect connectors that need re-authentication or have stopped syncing\n- **Automation** — programmatically surface connector health in dashboards or alerting systems\n",
"operationId": "listConnectors",
"x-mcp-name": "connectors-list",
"tags": [
"Connectors"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorsListResponse"
},
"examples": {
"all_connected": {
"summary": "Organization with both connectors active",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "connected"
},
{
"source": "hubspotApp",
"status": "connected"
}
]
}
},
"needs_reauth": {
"summary": "LinkedIn Sales Navigator requiring re-authentication",
"value": {
"connectors": [
{
"source": "salesNavigator",
"status": "needs-authentication"
}
]
}
},
"empty": {
"summary": "No connectors configured",
"value": {
"connectors": []
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/credits": {
"get": {
"summary": "Get remaining credit balance",
"x-mcp-description": "Check how many Saber API credits your organization has left in the current billing period.",
"description": "Returns the remaining credits balance for your organization.\n\nUse this endpoint to check how many credits you have available before running large batches\nor to monitor usage programmatically.\n\n---\n\n## What Are Credits?\n\nCredits are the single currency for every metered Saber API operation. Current costs:\n- **Company & contact signals** (`POST /v1/companies/signals[/sync|/batch]`, `POST /v1/contacts/signals[/sync]`, and their entity-first aliases `POST /v1/companies/enrich/custom[/batch]` and `POST /v1/contacts/enrich/custom`) — **2 credits** per signal (batch: per signal created); repeat calls served from cache are free\n- **Prebuilt signals** (`POST /v1/signals/[sync/]{funding|mna|tech|open-jobs|firmographics}`) — **2 credits** per result delivered to your organization\n- **Prebuilt enrichment** (`POST /v1/companies/enrich/{type}`) — priced per type: `funding` **0.3**, `open-jobs` **0.5**, `firmographics` **0.6**, `mna` **0.75**, `tech-stack` **0.75** credits per result delivered; cached results free, including results already paid for via the legacy signals surface\n- **Signal subscription runs** — the schedule/trigger is free; every signal a run freshly generates charges **2 credits** (cached results free)\n- **Contact research** (`POST /v1/contacts/research`) — **2 credits** per request\n- **Email finder** (`POST /v1/contacts/find-email`) — **0.1 credits** per successfully processed request; a found email and a verified not-found both charge (the verification work ran either way), errors are free\n- **Market signals** — subscriptions are free to configure; each signal created (stored) by a polling run charges **8 credits**, independent of webhook delivery\n- **Company list creation with a `technologies` filter** — **1 credit** per matched company, charged on the creation-time estimate; preview the same estimate for free via `POST /v1/companies/lists/count-preview`\n- **Everything else** — reads, listing, templates, summaries, subscription configuration and lifecycle — is free\n\nFailed signal runs, failed research jobs, and rejected email-finder requests are not charged. Market-signal charges apply once a signal is stored, independent of webhook delivery. Credits reset at the start of each billing period.\n\n---\n\n## When to Use This Endpoint\n\n- **Before large batches:** Check that you have enough credits before submitting a bulk request\n- **Monitoring dashboards:** Poll periodically to display current usage\n- **Conditional logic:** Skip signal generation if credits are exhausted\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/credits \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getCredits",
"x-mcp-name": "credits-get_balance",
"tags": [
"Billing"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Credit balance retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreditsBalance"
},
"examples": {
"credits_available": {
"summary": "Organization has credits remaining (may be fractional)",
"value": {
"remainingCredits": 57.9
}
},
"credits_low": {
"summary": "Organization is running low on credits",
"value": {
"remainingCredits": 3
}
},
"credits_exhausted": {
"summary": "No credits remaining",
"value": {
"remainingCredits": 0
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/organisation": {
"get": {
"summary": "Get your organisation profile",
"description": "Returns the authenticated organisation's profile data: name, website, and description fields.\n\nThis endpoint is primarily intended for use with the Saber CLI and MCP server — allowing\ntools like AI coding assistants to automatically pull in your organisation context (products, use cases,\nvalue proposition) without manual copy-paste.\n\n---\n\n## Example\n\n```bash\ncurl https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n",
"operationId": "getOrganisation",
"x-mcp-name": "organisation-get",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"responses": {
"200": {
"description": "Organisation profile retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
},
"examples": {
"example": {
"summary": "Organisation with full description",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update your organisation profile",
"description": "Partially updates the authenticated organisation's profile. All fields are optional — only\nthe fields you include will be updated. Omitted fields are left unchanged.\n\nDescription sub-fields (`general`, `products`, `useCases`, `valueProp`) can also be updated\nindividually — sending only `description.general` will not affect the other description fields.\n\n---\n\n## Example\n\n```bash\ncurl -X PUT https://api.saber.app/v1/organisation \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": {\"general\": \"Updated company overview.\"}}'\n```\n",
"operationId": "updateOrganisation",
"x-mcp-name": "organisation-update",
"tags": [
"Organisation"
],
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganisationRequest"
},
"examples": {
"update_name": {
"summary": "Update only the organisation name",
"value": {
"name": "New Company Name"
}
},
"update_description": {
"summary": "Update a single description sub-field",
"value": {
"description": {
"general": "Updated company overview."
}
}
},
"full_update": {
"summary": "Update multiple fields at once",
"value": {
"name": "Acme Corp",
"website": "acme.com",
"description": {
"general": "Acme Corp builds enterprise software for logistics teams.",
"products": "Route optimisation platform and real-time freight tracking.",
"useCases": "Reducing delivery costs for 3PL operators and e-commerce brands.",
"valueProp": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Organisation profile updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Organisation"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/import": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Import a company list from HubSpot",
"x-mcp-description": "Import companies from a HubSpot list, view, or segment into a saved Saber company list.",
"operationId": "importCompanyList",
"x-mcp-name": "company_lists-import",
"description": "Create a company list by importing companies directly from a connected\nHubSpot portal using a property filter.\n\nThe endpoint queries the HubSpot CRM for companies matching the filter,\nupserts stub records in Saber so the list is immediately queryable, and\nreturns the created list. Stub records are enriched asynchronously by\nthe normal background enrichment process (LinkedIn followers, employee\ncounts, etc.) — this typically completes within minutes to hours.\n\nIf some company stubs fail to upsert, those domains are excluded from\nthe list and returned in `unresolvedDomains` so callers can surface a\nwarning to the user.\n\n**Requires a connected HubSpot installation.** Returns `400` if no\nHubSpot portal is linked to the authenticated organization.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListRequest"
},
"example": {
"name": "HubSpot — Technology companies",
"source": {
"type": "hubspot",
"filter": {
"propertyName": "industry",
"operator": "EQ",
"value": "Technology"
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list imported and created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportCompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request — no HubSpot installation connected, or invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity — filter matched zero companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests — rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/count-preview": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Preview expected company count and credit cost",
"x-mcp-description": "Estimate how many companies match a list filter and how many credits the list operation would cost.",
"operationId": "countPreviewCompanyList",
"x-mcp-name": "company_lists-count_preview",
"description": "Returns an **estimate** of how many companies match a given filter and\nthe Saber credit cost to create that list — without creating the list\nor charging any credits.\n\nWhen `technologies` is present the count is fetched from Theirstack in\nblur mode (free), so you can show the user \"X companies match, ~Y\ncredits\" before they commit. Blur-mode counts are estimates: list\ncreation charges based on the same estimate, and the final list size\ncan differ slightly. For filters without `technologies` the count is a\nfast database query (always free, and exact).\n\nResults are cached for 5 minutes per caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"technologies": [
"stripe"
],
"location": {
"countryCodes": [
"NL"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Count and credit cost returned successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"count",
"credits"
],
"properties": {
"count": {
"type": "integer",
"description": "Number of companies expected to match the filter",
"example": 142
},
"credits": {
"type": "integer",
"description": "Saber credits that will be charged when the list is created",
"example": 142
}
}
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Create a company list",
"x-mcp-description": "Save a company filter as a reusable list for review, enrichments, scoring, export, and CRM workflows.",
"operationId": "createCompanyList",
"x-mcp-name": "company_lists-create",
"description": "Create a named company list with a filter definition. The filter criteria\ndetermine which companies belong to the list.\n\nCreating a list whose filter includes `technologies` charges\n**1 credit per matched company** to reveal the matching companies —\ncharged once at creation; subsequent reads of the list are free.\nThe charge is based on the match-count **estimate** at creation time;\nblur-mode counting, deduplication, and the fetch page cap mean the\nfinal list size can differ slightly from the amount charged.\n`POST /v1/companies/lists/count-preview` returns the same free\nestimate — call it first to show \"X companies match, ~Y credits\"\nbefore committing. Lists without a `technologies` filter are free to\ncreate.\n\nResults are cached for 5 minutes per caller. Mutations (create, update,\ndelete) automatically invalidate the cache for the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateCompanyListRequest"
},
"example": {
"name": "Mid-size US SaaS companies",
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Company list created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters (e.g. empty or too-long name)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"tags": [
"Company Lists"
],
"summary": "List company lists",
"x-mcp-description": "List saved company lists with pagination.",
"operationId": "listCompanyLists",
"x-mcp-name": "company_lists-list",
"description": "Retrieve a paginated list of company lists belonging to the authenticated caller.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of lists to return (1–100, default 20)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
},
{
"name": "offset",
"in": "query",
"description": "Number of lists to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Company lists retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListsResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get a company list by ID",
"x-mcp-description": "Retrieve company list metadata, including its filter and current company count.",
"operationId": "getCompanyList",
"x-mcp-name": "company_lists-get",
"description": "Retrieve a single company list including its name and filter definition.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company list retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": [
"Company Lists"
],
"summary": "Update a company list",
"x-mcp-description": "Update a company list name or filter and refresh cached matching companies.",
"operationId": "updateCompanyList",
"x-mcp-name": "company_lists-update",
"description": "Update the name and/or filter of an existing company list. Both fields\nare required in the request body (full replacement, not a patch).\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to update",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateCompanyListRequest"
},
"example": {
"name": "Updated list name",
"filter": {
"industries": [
"technology, information and internet"
],
"sizes": [
"201-500"
]
}
}
}
}
},
"responses": {
"200": {
"description": "Company list updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": [
"Company Lists"
],
"summary": "Delete a company list",
"x-mcp-description": "Delete a saved company list and remove its reusable segment boundary.",
"operationId": "deleteCompanyList",
"x-mcp-name": "company_lists-delete",
"description": "Soft-delete a company list. The list will no longer appear in list\nresults. This operation cannot be undone via the API.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list to delete",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Company list deleted successfully"
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/companies": {
"get": {
"tags": [
"Company Lists"
],
"summary": "Get companies in a list",
"x-mcp-description": "Page through companies that match a saved company list filter.",
"operationId": "getCompanyListCompanies",
"x-mcp-name": "company_lists-get_companies",
"description": "Retrieve companies that belong to a company list, paginated. Companies\nare returned with basic profile information and optional LinkedIn\nenrichment data.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"description": "Maximum number of companies to return (1–100, default 25)",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
},
{
"name": "offset",
"in": "query",
"description": "Number of companies to skip for pagination (default 0)",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "Companies retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanyListCompaniesResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/lists/{listId}/export": {
"post": {
"tags": [
"Company Lists"
],
"summary": "Export a company list as CSV",
"x-mcp-description": "Export a saved company list to CSV, optionally including selected firmographic and enrichment columns.",
"operationId": "exportCompanyList",
"x-mcp-name": "company_lists-export",
"description": "Export all companies in a list as a CSV file. Returns a\n`text/csv` file with one row per company.\n\n**Fixed columns** (always present): `name`, `website`\n\n**Optional firmographic columns** — specify via the `fields` array in\nany order. Allowed values: `industry`, `size`, `founded`, `city`,\n`state`, `country_code`. Unknown values return 422.\n\n**Signal columns** — specify `signalTemplateIds` to include two\ncolumns per matching subscription: `{signal_name}_answer` and\n`{signal_name}_confidence`. If `signalTemplateIds` is absent or empty,\nno signal columns are added.\n\nThe export is truncated to the first 10,000 companies. No credits are charged.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "listId",
"in": "path",
"required": true,
"description": "The unique identifier of the company list",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExportCompanyListRequest"
},
"examples": {
"firmographics_only": {
"summary": "Firmographic columns, no signals",
"value": {
"fields": [
"industry",
"size",
"country_code"
]
}
},
"with_signals": {
"summary": "Firmographic columns + two signal subscriptions",
"value": {
"fields": [
"industry",
"founded"
],
"signalTemplateIds": [
"123e4567-e89b-12d3-a456-426614174000",
"789e0123-e89b-12d3-a456-426614174001"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "CSV file streamed successfully",
"content": {
"text/csv": {
"schema": {
"type": "string",
"format": "binary"
},
"example": "name,website,industry,size,is_hiring_answer,is_hiring_confidence\nAcme Corp,acme.com,Software,51-200,true,0.92\nGlobex,globex.com,Manufacturing,201-500,,\n"
}
},
"headers": {
"Content-Disposition": {
"description": "Attachment filename derived from the list name",
"schema": {
"type": "string",
"example": "attachment; filename=\"My List.csv\""
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Company list not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Unprocessable Entity - Invalid field name or list exceeds 10,000 companies",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/companies/search": {
"post": {
"tags": [
"Companies"
],
"summary": "Search companies matching a filter",
"x-mcp-description": "Preview up to 50 companies that match a filter before creating a reusable company list.",
"operationId": "searchCompanies",
"x-mcp-name": "company_lists-search",
"description": "Returns up to 50 companies that match the given filter criteria.\n\nResults are cached for 5 minutes per unique filter. Identical filters\nwill return cached data during this window.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchRequest"
},
"example": {
"filter": {
"industries": [
"software development"
],
"sizes": [
"51-200",
"201-500"
],
"location": {
"countryCodes": [
"US"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Companies matching the filter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompanySearchResponse"
}
}
}
},
"400": {
"description": "Bad Request - Invalid filter parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized - Invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Too Many Requests - Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles": {
"post": {
"summary": "Create a scoring profile",
"description": "Create a named, org-scoped scoring profile. A profile groups scoring rules for a\nsingle object type (`company` or `contact`) and produces fit/urgency scores\nfor any object assigned to it. A single object can be assigned to multiple\nprofiles of the matching type.\n",
"operationId": "createScoringProfile",
"x-mcp-name": "scoring-profile-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScoringProfileRequest"
}
}
}
},
"responses": {
"201": {
"description": "Profile created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"401": {
"description": "Unauthorized — invalid or missing API key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring profiles",
"description": "List all scoring profiles for the authenticated organisation.",
"operationId": "listScoringProfiles",
"x-mcp-name": "scoring-profile-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profiles retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"get": {
"summary": "Get a scoring profile",
"operationId": "getScoringProfile",
"x-mcp-name": "scoring-profile-get",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Profile retrieved",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"summary": "Update a scoring profile",
"description": "Rename or re-describe a profile. Profile type is immutable.",
"operationId": "updateScoringProfile",
"x-mcp-name": "scoring-profile-update",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateScoringProfileRequest"
}
}
}
},
"responses": {
"200": {
"description": "Profile updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringProfile"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"summary": "Delete a scoring profile",
"description": "Deletes the profile and cascades to its rules, assignments, and score results.\n",
"operationId": "deleteScoringProfile",
"x-mcp-name": "scoring-profile-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Profile deleted"
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"put": {
"summary": "Upsert a scoring rule",
"description": "Create or replace the rule for `(profileId, signalTemplateId, dimension)`. Triggers\na recompute of every object assigned to the profile so existing scores reflect\nthe new rule immediately.\n\nThe shape of `pointValues` depends on the answer type of the referenced\nsignal template:\n\n* **boolean** — `{ \"true\": 20, \"false\": -5 }`\n* **number / percentage / currency** — `{ \"ranges\": [{ \"min\": 0, \"max\": 500, \"points\": 15 }] }` (upper bound is exclusive)\n* **list** — `{ \"choices\": { \"Salesforce\": 10, \"HubSpot\": 8 }, \"mode\": \"additive\" }` — `mode` is optional; supported values are `additive` (default), `best-match`, `contains-all`, `contains-none`, and `exact-match`.\n",
"operationId": "upsertScoringRule",
"x-mcp-name": "scoring-rule-upsert",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertScoringRuleRequest"
}
}
}
},
"responses": {
"200": {
"description": "Rule upserted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List scoring rules for a profile",
"operationId": "listScoringRules",
"x-mcp-name": "scoring-rule-list",
"tags": [
"Scoring"
],
"responses": {
"200": {
"description": "Rules retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoringRule"
}
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/profiles/{profileId}/rules/{ruleId}": {
"parameters": [
{
"name": "profileId",
"in": "path",
"required": true,
"description": "Scoring profile UUID",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "ruleId",
"in": "path",
"required": true,
"description": "Scoring rule UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Delete a scoring rule",
"description": "Removes the rule and triggers a recompute of every object assigned to the\nprofile so scores never silently go stale.\n",
"operationId": "deleteScoringRule",
"x-mcp-name": "scoring-rule-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Rule deleted"
},
"404": {
"description": "Rule or profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments": {
"post": {
"summary": "Assign a profile to a single object",
"description": "Links one company or contact to a scoring profile. Triggers immediate score\ncomputation so the score appears without waiting for the next signal run.\nReturns 422 if the object type does not match the profile's configured type.\n",
"operationId": "createProfileAssignment",
"x-mcp-name": "scoring-assignment-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProfileAssignmentRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignment created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
},
"404": {
"description": "Profile not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "Object already assigned to this profile",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"get": {
"summary": "List profile assignments for an object",
"description": "Returns every profile a given object is assigned to.",
"operationId": "listProfileAssignments",
"x-mcp-name": "scoring-assignment-list",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"description": "Object type (`company` or `contact`)",
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "For `company` use the domain (e.g. `acme.com`); for `contact` use the LinkedIn profile URL",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Assignments retrieved",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/bulk": {
"post": {
"summary": "Assign a profile to many objects at once",
"description": "Links many company or contact IDs to a single profile in one call. Skips\nduplicates (returns only newly created rows) and triggers an immediate\nscore computation per newly assigned object.\n",
"operationId": "bulkCreateProfileAssignments",
"x-mcp-name": "scoring-assignment-bulk-create",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkCreateProfileAssignmentsRequest"
}
}
}
},
"responses": {
"201": {
"description": "Assignments created (duplicates omitted from the response)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProfileAssignment"
}
}
}
}
},
"422": {
"description": "Validation error (including profile/object type mismatch)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/assignments/{assignmentId}": {
"parameters": [
{
"name": "assignmentId",
"in": "path",
"required": true,
"description": "Profile assignment UUID",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"delete": {
"summary": "Remove a profile assignment",
"description": "Removes the assignment and cleans up any score results computed for this\n`(profile, object)` pair.\n",
"operationId": "deleteProfileAssignment",
"x-mcp-name": "scoring-assignment-delete",
"tags": [
"Scoring"
],
"responses": {
"204": {
"description": "Assignment deleted"
},
"404": {
"description": "Assignment not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/scores": {
"get": {
"summary": "Read scores for one or more objects",
"description": "Returns one row per `(profile, object, dimension)` triple. Pass `objectId`\nmultiple times to read scores for several objects in a single call. The\nresponse includes the per-rule contribution breakdown so the score is always\nexplainable.\n",
"operationId": "getScores",
"x-mcp-name": "scoring-scores-get",
"tags": [
"Scoring"
],
"parameters": [
{
"name": "objectType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"company",
"contact"
]
}
},
{
"name": "objectId",
"in": "query",
"required": true,
"description": "Repeatable. For `company` use the domain; for `contact` use the LinkedIn profile URL.",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Scores retrieved (empty array if no scores exist for the given objects)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreResult"
}
}
}
}
},
"422": {
"description": "Missing or invalid query parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/scoring/compute": {
"post": {
"summary": "Trigger score recomputation",
"description": "Triggers score recomputation for the given objects against the latest\nsignal data. Idempotent — duplicate triggers for the same object are\ndeduplicated automatically.\n\nReturns `202 Accepted` with `{queued, failed}` counts as soon as\ndispatches finish (recomputation runs asynchronously; results become\navailable via `GET /v1/scoring/scores`). A non-zero `failed` means\nsome dispatches failed; if *every* dispatch fails the endpoint\nreturns 502 instead.\n",
"operationId": "triggerScoreCompute",
"x-mcp-name": "scoring-compute",
"tags": [
"Scoring"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresRequest"
}
}
}
},
"responses": {
"202": {
"description": "Accepted — at least one recompute job was queued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ComputeScoresAcceptedResponse"
}
}
}
},
"422": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "All recompute dispatches failed.\nError code is `RECOMPUTE_DISPATCH_FAILED`. Safe to retry.\n",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API key authentication using Bearer token. Format: sk_live_ followed by a secure random string."
}
},
"parameters": {
"SyncTimeoutSec": {
"name": "X-Sbr-Timeout-Sec",
"in": "header",
"required": false,
"description": "Custom timeout in seconds (max 870 — higher values are clamped). When absent, the server-configured default (870s) applies.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 870
},
"example": 120
},
"SyncTimeoutError": {
"name": "X-Sbr-Timeout-Error",
"in": "header",
"required": false,
"description": "If \"true\", returns 408 Request Timeout on timeout instead of 200 with the latest (still-`processing`) status.\nException: after the server has started keep-alive (whitespace sent to keep a long request open), the status\nis already 200, so a timeout returns 200 with a non-terminal `status` in the body.\n",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"example": "false"
}
},
"schemas": {
"CompanyDomainResolutionRequest": {
"type": "object",
"required": [
"companyName"
],
"properties": {
"companyName": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Company, brand or product name to resolve to its operating company. Include the legal suffix when known."
},
"countryCode": {
"type": "string",
"pattern": "^[A-Za-z]{2}$",
"description": "Optional ISO 3166-1 alpha-2 country hint, normalized to uppercase."
},
"context": {
"type": "string",
"maxLength": 10000,
"description": "Optional article excerpt or identity details used to distinguish namesakes."
}
}
},
"CompanyDomainResolutionResult": {
"type": "object",
"required": [
"status",
"domain",
"reason",
"candidates"
],
"properties": {
"status": {
"type": "string",
"enum": [
"resolved",
"ambiguous",
"not_found",
"incomplete"
]
},
"domain": {
"type": [
"string",
"null"
],
"description": "Selected lowercase hostname, without scheme, www, path or port. Null unless resolved."
},
"reason": {
"type": "string",
"description": "Match decision and any evidence limitations."
},
"candidates": {
"type": "array",
"maxItems": 3,
"description": "One candidate when resolved, two or three when ambiguous, otherwise empty.",
"items": {
"type": "object",
"required": [
"companyName",
"companyDescription",
"legalName",
"domain",
"companyInfoUrl",
"countryCode",
"matchReason",
"evidence"
],
"properties": {
"companyName": {
"type": "string",
"description": "Supported name of the resolved operating company, not necessarily its legal name. A named subsidiary is not replaced by its parent."
},
"companyDescription": {
"type": [
"string",
"null"
],
"description": "Optional neutral description of this candidate's business from captured sources. The agent is guided to use at most five concrete sentences describing its products, customers and supported distinguishing facts, without marketing language or an enforced length limit. Null when unsupported. Supplied context alone is not verified fact; research is not extended solely to write this description."
},
"legalName": {
"type": [
"string",
"null"
],
"description": "Exact legal name of the resolved company when explicitly supported by an official source, otherwise null. Research is not extended solely to obtain it; a regional subsidiary's name is not substituted."
},
"domain": {
"type": "string",
"description": "The resolved company's primary domain, even if it also hosts the main product. Not an About or investor-relations hostname selected merely for its company information."
},
"companyInfoUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "Exact fetched official About or company-overview URL for this company, possibly on another domain. Null if unavailable; never a guessed path."
},
"countryCode": {
"type": [
"string",
"null"
],
"description": "Supported uppercase ISO 3166-1 alpha-2 country code, or null when unknown."
},
"matchReason": {
"type": "string",
"description": "How this company matches the name and supplied context, including any alias or brand/product relationship."
},
"evidence": {
"type": "array",
"description": "Captured citations retained by source validation. Empty when none are available; citation loss alone does not invalidate the answer.",
"maxItems": 3,
"items": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
}
}
}
}
}
}
}
}
},
"Monitor": {
"type": "object",
"required": [
"id",
"object",
"kind",
"status",
"checks",
"target"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"monitor"
]
},
"kind": {
"type": "string",
"enum": [
"watch",
"discovery"
],
"description": "watch = re-checks entities on a list; discovery = watches the market for occurrences."
},
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"active",
"paused"
]
},
"checks": {
"type": "array",
"description": "Exactly one element at launch (decision 25).",
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
},
"lastRunAt": {
"type": "string",
"format": "date-time"
},
"nextRunAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"MonitorCheck": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"enrichment",
"signal"
]
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Enrichment checks — the template holding the question."
},
"type": {
"type": "string",
"enum": [
"job_posted",
"social_posted",
"funding_raised",
"investment_made",
"ipo_announced",
"ipo_stage_changed",
"repo_engaged",
"ipo_rumoured",
"tender_published"
],
"description": "Signal checks — the occurrence type to watch for (verb-past vocabulary)."
},
"prompt": {
"type": "string",
"description": "Signal checks — natural-language intent the AI turns into filters."
},
"filters": {
"type": "object",
"description": "Signal checks — structured filters (type-specific)."
}
}
},
"MonitorTarget": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list",
"market"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "List targets — the watched list."
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "List targets — what the list contains."
}
}
},
"MonitorSchedule": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "Alternative to frequency; minimum interval 24h."
},
"timezone": {
"type": "string"
}
}
},
"MonitorWebhook": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "Delivery endpoint. Omit it to create a monitor with no delivery: signals are still detected, stored and charged, and are read back from GET /v1/monitors/{monitorId}/signals instead of being POSTed. A url-less webhook block is still accepted, and is how a caller sets interval and intervalSignalLimit without configuring delivery."
},
"intervalSignalLimit": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum signals to deliver per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling."
},
"interval": {
"type": "string",
"enum": [
"daily",
"weekly"
]
}
}
},
"CreateMonitorRequest": {
"type": "object",
"required": [
"checks"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
}
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"allOf": [
{
"$ref": "#/components/schemas/MonitorWebhook"
},
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"maxLength": 200,
"writeOnly": true,
"description": "Optional HMAC signing secret for deliveries; never echoed back."
}
}
}
],
"description": "Discovery monitors — delivery webhook."
}
}
},
"UpdateMonitorRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"checks": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/components/schemas/MonitorCheck"
},
"description": "Discovery monitors — update the check's prompt/filters (type is immutable)."
},
"target": {
"$ref": "#/components/schemas/MonitorTarget"
},
"schedule": {
"$ref": "#/components/schemas/MonitorSchedule"
},
"webhook": {
"$ref": "#/components/schemas/MonitorWebhook"
}
}
},
"MonitorSignal": {
"type": "object",
"required": [
"id",
"object",
"monitorId",
"type",
"observedAt"
],
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"signal"
]
},
"monitorId": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string",
"description": "Verb-past occurrence type: `result_changed` (watch monitors) or the discovery type (`job_posted`, `social_posted`, `funding_raised`, `investment_made`, `ipo_announced`, `ipo_stage_changed`, `repo_engaged`, `ipo_rumoured`, `tender_published`)."
},
"occurredAt": {
"type": "string",
"format": "date-time",
"description": "When the occurrence happened in the world (when known)."
},
"observedAt": {
"type": "string",
"format": "date-time",
"description": "When Saber detected it."
},
"entity": {
"type": "object",
"description": "The entity the signal is about, when known. Company-list watch occurrences carry `domain` and/or `companyId`; contact-list watch occurrences carry `contactProfileUrl`."
},
"payload": {
"type": "object",
"description": "Type-specific detail."
}
}
},
"FindEmailRequest": {
"type": "object",
"required": [
"full_name",
"domain"
],
"properties": {
"full_name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Full name of the contact. Accepts 2-4 token names and\nEuropean name particles (`Joey van Ommen`,\n`Joey van der Ommen`). Trailing credentials and\nhonorifics (`Ahmed Othman, MBA, CDMP`, `Dr. …`),\nparenthetical roles, and mixed-script tails are cleaned\noff before parsing, so those resolve rather than being\nrejected. Non-Latin names (Arabic, Chinese, etc.) are\ntransliterated and attempted best-effort as\nlow-confidence parses. A value that is actually a\ncompany name (`Best Vision Business Center`) settles as\n`not_found` (not an error). A genuinely unparseable\nname — a single lone token, or empty — is rejected with\n422.\n",
"example": "Joey van Ommen"
},
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"description": "The mail domain for the contact's company. Accepts\nbare domains (`saber.app`), domains with `www.`\nprefix, and trailing-dot forms. URL-shaped inputs\n(`https://saber.app`) and email-shaped inputs\n(`joey@saber.app`) are rejected with 422.\n\nIf the domain redirects (a rebrand) or is dead (no\nmail), the finder resolves the company's real domain\nand searches there too — supplying `company_name`\nand/or `location` makes that discovery far more\naccurate.\n",
"example": "saber.app"
},
"company_name": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's company name. Used only when\nthe supplied `domain` is dead or redirects: it anchors\nthe web search that finds the company's real domain,\nand sharply reduces wrong-company matches. Strongly\nrecommended whenever the domain may be stale.\n",
"example": "Saber"
},
"location": {
"type": "string",
"maxLength": 200,
"description": "Optional. The contact's location (city / region /\ncountry). Enables location-aware candidate generation\nfor global companies — e.g. a London contact at a firm\nwith country domains resolves to the regional domain\nor subdomain.\n",
"example": "London, United Kingdom"
}
}
},
"FindEmailResponse": {
"type": "object",
"required": [
"status",
"email",
"verification"
],
"properties": {
"status": {
"type": "string",
"enum": [
"found",
"not_found",
"inconclusive"
],
"description": "Explicit outcome discriminator. `found` → `email`\nand `verification` are present; `not_found` and\n`inconclusive` → both are `null`. `inconclusive`\nmeans \"reachable, couldn't confirm now — retry\nlater\" (greylisting / budget exhausted), distinct\nfrom a definitive `not_found`. Branch on this\ninstead of null-checking `verification`.\n"
},
"email": {
"type": [
"string",
"null"
],
"description": "The verified email, or `null` when no deliverable\nmailbox was found within the budget.\n",
"example": "joey.vanommen@saber.app"
},
"verification": {
"type": [
"object",
"null"
],
"description": "Verification metadata for the returned email, or\n`null` when `email` is `null`.\n",
"required": [
"state",
"score",
"accept_all"
],
"properties": {
"state": {
"type": "string",
"enum": [
"deliverable",
"risky"
],
"description": "Verification verdict. `deliverable` is a\nconfident hit; `risky` means the mailbox\naccepts mail but the verifier couldn't\nfully validate it.\n"
},
"score": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Verifier's confidence score (0-100)."
},
"accept_all": {
"type": "boolean",
"description": "True when the domain accepts all mail (catch-\nall). The returned email is our best guess\nunder the catch-all but cannot be\ndistinguished from a non-existent mailbox.\n"
}
}
}
}
},
"PrebuiltSignalRequest": {
"type": "object",
"description": "Request body for a prebuilt signal. You supply the company\n`domain` (plus optional delivery and refresh controls); the signal\nreturns its own documented, structured answer.\n",
"required": [
"domain"
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional webhook to receive the completed signal. Intended for the\nasync endpoints; if supplied on a synchronous `/sync/` endpoint the\nwebhook is still delivered, in addition to the blocking response.\n",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"TechSignalRequest": {
"type": "object",
"description": "Request body for the Tech signal. Supply a `domain` plus **exactly one**\nof `category` or `technology` — providing neither or both is a 422.\n",
"required": [
"domain"
],
"oneOf": [
{
"required": [
"category"
],
"not": {
"required": [
"technology"
]
}
},
{
"required": [
"technology"
],
"not": {
"required": [
"category"
]
}
}
],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\").",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"category": {
"type": "string",
"description": "A coarse technology category to survey. Mutually exclusive with\n`technology`.\n",
"enum": [
"erp",
"crm"
],
"example": "crm"
},
"technology": {
"type": "string",
"description": "A specific technology to check for, given as free text (e.g.\n\"salesforce\", \"SAP S/4HANA\"); Saber resolves it to a canonical\nregistry slug. An unresolvable value returns 422 with did-you-mean\nsuggestions. Mutually exclusive with `category`.\n",
"example": "salesforce"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "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.",
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "Force a fresh run. By default an identical request (same domain and\nparameters) returns the existing result instead of re-running; set\nthis to `true` to bypass that and research again.\n",
"default": false
}
}
},
"FundingSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Funding signal.",
"required": [
"hasFundingEvent",
"isPublic"
],
"properties": {
"hasFundingEvent": {
"type": "boolean",
"description": "True if the company has any known funding event."
},
"isPublic": {
"type": "boolean",
"description": "True if the company has gone public (IPO / direct listing / SPAC merger)."
},
"latestRound": {
"type": "string",
"description": "The most recent primary equity round or stage. `Unknown` when the specific stage cannot be identified. Never a secondary-market, tender, or debt event.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Unknown"
]
},
"latestEvents": {
"type": "array",
"description": "The latest 1-3 funding events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"eventType"
],
"properties": {
"eventType": {
"type": "string",
"description": "The coarse kind of capital event.",
"enum": [
"funding_round",
"ipo",
"secondary",
"debt",
"grant"
]
},
"amount": {
"type": "number",
"description": "Amount raised in the reported currency (for an IPO, the proceeds raised — not the market capitalisation); omitted when not reported."
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code (e.g. USD)."
},
"round": {
"type": "string",
"description": "Funding round / stage.",
"enum": [
"Pre-Seed",
"Seed",
"Angel",
"Series A",
"Series B",
"Series C",
"Series D",
"Series E",
"Series F",
"Series G",
"Series H",
"Series I",
"Series J",
"Venture Round",
"Growth Equity",
"Private Equity",
"IPO",
"Secondary Market",
"Tender Offer",
"Post-IPO Equity",
"Post-IPO Debt",
"Debt Financing",
"Convertible Note",
"Corporate Round",
"Grant",
"Equity Crowdfunding",
"Unknown"
]
},
"investors": {
"type": "array",
"description": "Lead and participating investors.",
"items": {
"type": "string"
}
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"MAndASignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed M&A signal.",
"required": [
"detected"
],
"properties": {
"detected": {
"type": "boolean",
"description": "True if at least one corroborated announced, completed, or terminated acquisition event was found (a rumored-only finding does not count)."
},
"latestEvents": {
"type": "array",
"description": "The 1-3 most relevant acquisition events, most recent first. Not necessarily a complete history.",
"items": {
"type": "object",
"required": [
"role",
"counterparty",
"status",
"amount",
"currency",
"announcedAt",
"sourceUrls"
],
"properties": {
"role": {
"type": "string",
"description": "The queried company's role — `acquiree` if it was bought, `acquirer` if it bought the counterparty.",
"enum": [
"acquirer",
"acquiree"
]
},
"counterparty": {
"type": "string",
"description": "The other company in the deal (the buyer when role=acquiree, the company bought when role=acquirer)."
},
"status": {
"type": "string",
"description": "Deal status — `completed` (closed), `announced` (agreed but not yet closed), `terminated` (announced then called off / blocked), `rumored` (reported but unconfirmed).",
"enum": [
"rumored",
"announced",
"completed",
"terminated"
]
},
"amount": {
"type": [
"number",
"null"
],
"minimum": 0.01,
"description": "Deal value in the reported currency; null when undisclosed (never 0)."
},
"currency": {
"type": [
"string",
"null"
],
"description": "ISO 4217 currency code (e.g. USD); null when amount is null."
},
"announcedAt": {
"type": "string",
"description": "Announcement date in ISO 8601 (YYYY-MM-DD)."
},
"sourceUrls": {
"type": "array",
"description": "Public source URLs corroborating the event.",
"items": {
"type": "string"
}
}
}
}
}
}
},
"TechSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Tech signal. Both the\n`category` and `technology` scopes return this shape; `scope` says which\nwas asked. Every reported technology cites public web evidence — vendor\ndata alone can only produce a `not_found` element.\n",
"required": [
"scope",
"technologies"
],
"properties": {
"scope": {
"type": "string",
"description": "What was asked — `category` (which systems in a category) or `technology` (does the company use a specific technology, a one-element `technologies` list).",
"enum": [
"category",
"technology"
]
},
"category": {
"type": "string",
"description": "The requested technology category. Present for category-scope answers.",
"enum": [
"erp",
"crm"
]
},
"technologies": {
"type": "array",
"description": "Technologies investigated, strongest evidence first. A vendor-hinted candidate that could not be verified appears with status `not_found` (an explicit element, never a silent drop); technologies with no prior are never listed.",
"items": {
"type": "object",
"required": [
"technology",
"status",
"evidence"
],
"properties": {
"technology": {
"type": "string",
"description": "Canonical technology slug from the technology registry (kebab-case, e.g. sap-s4hana, salesforce)."
},
"displayName": {
"type": "string",
"description": "Human-readable product name (e.g. \"SAP S/4HANA\")."
},
"confidence": {
"type": "number",
"description": "0-1 confidence in the stated status, given the evidence."
},
"status": {
"type": "string",
"description": "`active` = current use with recent public evidence; `churned` = evidence shows the company moved off this technology; `not_found` = checked (e.g. a vendor-data hint) but no public evidence of current use.",
"enum": [
"active",
"churned",
"not_found"
]
},
"lastEvidence": {
"type": "string",
"description": "Date of the most recent corroborating evidence, ISO 8601 (YYYY-MM or YYYY-MM-DD)."
},
"evidence": {
"type": "array",
"description": "Public web evidence for this technology. A live website fingerprint counts as public evidence. Vendor/technographics job-data rows are not evidence; a `not_found` element has an empty evidence list.",
"items": {
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"description": "What kind of public source this is. `website_fingerprint` = a technology observed live on the company's own website (script/cookie/header/meta/dom detection).",
"enum": [
"job_posting",
"engineering_blog",
"docs",
"case_study",
"website_fingerprint"
]
},
"url": {
"type": "string",
"description": "Public URL of the source."
},
"title": {
"type": "string",
"description": "Title of the source page or posting."
}
}
}
}
}
}
},
"primary": {
"type": "string",
"description": "Canonical slug of the single current system of record for the requested category; omitted when no clear primary exists."
}
}
},
"OpenJobsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Open Jobs signal.",
"required": [
"openings",
"topTitles",
"source"
],
"properties": {
"openings": {
"type": "integer",
"description": "Total open roles posted in the last 90 days — the search tool's reported total match count for the window, not the number of sampled listings. 0 if none."
},
"topTitles": {
"type": "array",
"description": "Representative verbatim job titles drawn from the sampled listings — examples, not a complete or proportional breakdown. Empty when openings=0.",
"items": {
"type": "string"
}
},
"hiringThemes": {
"type": "array",
"description": "Function areas the sampled roles fall into, each from a fixed taxonomy. Examples of where the company is investing, not ranked or counted. Empty when openings=0.",
"items": {
"type": "string",
"enum": [
"Engineering",
"Data/ML",
"Product",
"Sales/GTM",
"Marketing",
"Operations",
"Finance/Legal",
"People/HR",
"Support",
"Other"
]
}
},
"techHiringFor": {
"type": "array",
"description": "Technologies named in the sampled postings (forward-looking hiring intent, distinct from the company's current stack). Examples, not exhaustive; [] when none are mentioned.",
"items": {
"type": "string"
}
},
"locations": {
"type": "array",
"description": "Representative locations from the sampled listings (city/region/country or \"Remote\"). Examples, not exhaustive.",
"items": {
"type": "string"
}
},
"latestPostingDate": {
"type": "string",
"description": "Most recent posting date among the sampled listings, ISO 8601 (YYYY-MM-DD); omitted when no postings."
},
"source": {
"type": "string",
"description": "Provenance of the hiring data; always `public_job_listings`.",
"enum": [
"public_job_listings"
]
}
}
},
"FirmographicsSignalAnswer": {
"type": "object",
"description": "Payload of `answer.jsonSchema` for a completed Firmographics signal.",
"required": [
"domain",
"saberCategory",
"segments"
],
"properties": {
"name": {
"type": "string",
"description": "Company name."
},
"domain": {
"type": "string",
"description": "Primary web domain (the identity anchor for the company)."
},
"website": {
"type": "string",
"description": "Primary website URL, taken verbatim from enrichment when present; researched only when absent."
},
"employeeCount": {
"type": "number",
"description": "Total headcount; omitted when it cannot be confirmed (never guessed)."
},
"sizeRange": {
"type": "string",
"description": "Employee band, e.g. \"51-200\"."
},
"type": {
"type": "string",
"description": "Ownership/entity type, e.g. Private, Public, Nonprofit, Government."
},
"foundedYear": {
"type": "number",
"description": "Year the company was founded."
},
"headquarters": {
"type": "object",
"description": "Primary HQ location.",
"properties": {
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"countryCode": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code."
}
}
},
"linkedInFollowers": {
"type": "number",
"description": "Public LinkedIn follower count, taken verbatim from enrichment; omitted when not provided."
},
"linkedinIndustry": {
"type": "string",
"description": "The raw coarse public industry tag, echoed for transparency (the prior, not the answer)."
},
"saberCategory": {
"type": "object",
"description": "Saber-derived classification that refines the coarse industry tag.",
"required": [
"primary",
"confidence"
],
"properties": {
"primary": {
"type": "string",
"description": "Generalized category, e.g. \"Construction Software\"."
},
"niche": {
"type": "string",
"description": "Specific vertical / sub-segment. Empty string if it cannot be narrowed."
},
"confidence": {
"type": "number",
"description": "Confidence in the category, 0-1."
},
"rationale": {
"type": "string",
"description": "One line on why this category."
},
"evidence": {
"type": "array",
"description": "1-3 public sources corroborating the category. Best-effort and optional.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Kind of source, e.g. company_site, case_study, press."
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
}
}
},
"segments": {
"type": "array",
"description": "Who the company sells to, ordered by dominance (first = primary). [] if undeterminable.",
"items": {
"type": "string",
"enum": [
"B2B",
"B2C",
"B2B2C",
"B2G",
"C2C"
]
}
},
"segmentRationale": {
"type": "string",
"description": "One line justifying the segment(s), especially multi-value or B2B2C."
}
}
},
"PrebuiltSignalBaseResponse": {
"type": "object",
"description": "Base fields for a prebuilt signal response. Prebuilt signals are\ncompany-scoped and, unlike custom signals, never echo a `question` —\nthe signal key is returned as `namedSignal` instead.\n",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Library signals are always company signals",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"namedSignal": {
"type": "string",
"description": "The prebuilt signal key this signal was created from, returned in place of `question` (e.g. \"funding\", \"mna\", \"tech\", \"open-jobs\", \"firmographics\").",
"example": "funding"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal (library signals always return `json_schema`)",
"enum": [
"json_schema"
],
"example": "json_schema"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2026-07-01T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"namedSignal",
"domain",
"createdAt"
]
},
"PrebuiltSignalDetailResponse": {
"description": "A prebuilt signal with its outcome fields (answer, confidence, sources, or failure details).",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalBaseResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"FundingSignalResult": {
"description": "A completed (or terminally failed) Funding signal. When completed, `answer.jsonSchema` is a FundingSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FundingSignalAnswer"
}
}
}
}
}
]
},
"MAndASignalResult": {
"description": "A completed (or terminally failed) M&A signal. When completed, `answer.jsonSchema` is an MAndASignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/MAndASignalAnswer"
}
}
}
}
}
]
},
"TechSignalResult": {
"description": "A completed (or terminally failed) Tech signal. When completed, `answer.jsonSchema` is a TechSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/TechSignalAnswer"
}
}
}
}
}
]
},
"OpenJobsSignalResult": {
"description": "A completed (or terminally failed) Open Jobs signal. When completed, `answer.jsonSchema` is an OpenJobsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/OpenJobsSignalAnswer"
}
}
}
}
}
]
},
"FirmographicsSignalResult": {
"description": "A completed (or terminally failed) Firmographics signal. When completed, `answer.jsonSchema` is a FirmographicsSignalAnswer.",
"allOf": [
{
"$ref": "#/components/schemas/PrebuiltSignalDetailResponse"
},
{
"type": "object",
"properties": {
"answer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
]
},
"jsonSchema": {
"$ref": "#/components/schemas/FirmographicsSignalAnswer"
}
}
}
}
}
]
},
"CreateMarketSignalSubscriptionRequest": {
"type": "object",
"required": [
"type",
"webhookUrl"
],
"properties": {
"type": {
"type": "string",
"description": "The type of market signal to monitor. Determines which data sources are polled\nand which filter schema is accepted.\n",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": "string",
"description": "Optional display name for the subscription",
"maxLength": 200,
"example": "DevOps hiring tracker"
},
"prompt": {
"type": "string",
"description": "Natural language prompt for AI-based filter generation (JOB_POSTS only).\nWhen provided without filters, the AI generates complete filters from the prompt.\nWhen provided with filters, the AI augments the filters with prompt intent.\nNot yet supported for LINKEDIN_POST.\n",
"example": "Find companies hiring machine learning engineers at seed to Series B startups in Europe"
},
"filters": {
"type": "object",
"description": "Subscription filters. The schema depends on the `type` field:\n\n**JOB_POSTS filters:**\n- `titleKeywords` (string[]): Natural language title matching\n- `excludeTitleKeywords` (string[]): Title exclusions\n- `titlePatterns` (string[]): Regex patterns for job titles\n- `excludeTitlePatterns` (string[]): Regex exclusion patterns for titles\n- `descriptionKeywords` (string[]): Description keyword matching\n- `excludeDescriptionKeywords` (string[]): Description exclusions\n- `descriptionPatterns` (string[]): Regex patterns for descriptions\n- `excludeDescriptionPatterns` (string[]): Regex exclusion patterns for descriptions\n- `countries` (string[]): ISO country codes for job location\n- `seniority` (string[]): Seniority levels (c_level, staff, senior, mid_level, junior)\n- `technologySlugs` (string[]): Required technologies\n- `minSalaryUsd` (number): Minimum annual salary in USD\n- `maxSalaryUsd` (number): Maximum annual salary in USD\n- `remote` (boolean): Filter for remote jobs only\n- `companyDomains` (string[]): Specific company domains to monitor\n- `excludeCompanyDomains` (string[]): Company domains to exclude\n- `companyNames` (string[]): Partial match company names\n- `excludeCompanyNames` (string[]): Company names to exclude\n- `minEmployees` (number): Minimum company size\n- `maxEmployees` (number): Maximum company size\n- `companyCountries` (string[]): Company HQ country codes\n- `companyTechnologySlugs` (string[]): Company tech stack filter\n- `excludeRecruitingAgencies` (boolean): Filter out staffing agencies\n- `fundingStages` (string[]): Funding stages (seed, series_a, series_b, etc.)\n- `industryIds` (string[]): LinkedIn industry IDs to include\n- `excludeIndustryIds` (string[]): LinkedIn industry IDs to exclude\n- `promptFilter` (string): AI-based relevance scoring prompt\n- `maxLookbackDays` (number): How far back to look in days\n\n**LINKEDIN_POST filters:**\n- `keywordsAll` (string[]): Boolean AND keywords\n- `keywordsAny` (string[]): Boolean OR keywords\n- `keywordsNone` (string[]): Boolean NOT keywords\n- `promptFilter` (string): AI-based content + persona filtering prompt\n- `maxLookbackDays` (number): Lookback period in days\n\n**GITHUB_REPO_ACTIVITY filters:**\n- `repos` (string[]): Watched repositories as `owner/name`\n- `orgs` (string[]): Watched organizations; every repository in the org is covered. `repos` plus `orgs` may name at most 5 targets in total\n- `activities` (string[]): `issue_opened` and/or `pr_opened`. Omit for both\n- `excludeAuthors` (string[]): GitHub logins to ignore. Bots and PUBLIC members of the watched organization are already excluded\n- `excludeProjectStaff` (boolean): Also drop authors whose resolved employer is the organization that owns the watched repository. Off by default. Most GitHub org memberships are private, so the public-member exclusion alone leaves a project's own maintainers in the results\n- `minConfidence` (number): Author-to-employer resolution floor, 0 to 1, applied before a signal is created and charged. Defaults to 0.7\n- `cooldownDays` (number): Suppression window for a (company, author) pair. A colleague at the same company still earns their own signal. Defaults to 30\n- `promptFilter` (string): AI-based relevance scoring prompt, judged on the item title and opening comment. Omit to keep every item\n- `maxLookbackDays` (number): Delivery lookback window in days. Items opened more than 30 days ago are never delivered, whatever this is set to\n\n**FUND_RAISED / RECENT_INVESTMENT / IPO filters (shared):**\n- `searchQueries` (string[]): Custom search queries\n- `keywords` (string[]): Keywords appended to default queries\n- `excludeKeywords` (string[]): Excluded keywords\n- `countries` (string[]): Geographic filtering (reserved)\n- `minAmountUsd` (number): Minimum deal size (reserved)\n- `maxLookbackDays` (number): Lookback period in days\n- `promptFilter` (string): AI-based relevance scoring prompt (reserved)\n- `sources` (string[]): Restrict discovery to specific data sources — `\"web\"` (news + RSS) and/or `\"edgar\"` (SEC filings). Omit for all sources. Honoured by the NestJS engine only.\n\n**IPO_LIFECYCLE filters:**\n- `eventTypes` (string[], required): Lifecycle stages to deliver — `filing`, `amendment`, `pricing`, `prospectus`, `listing`, `form5500`. `listing` is the company reaching the market without a priced offering (a direct listing, or a registration declared effective with a trading date announced); it carries no offer price, offer size or proceeds, because none exist, and a direct listing files no final prospectus at all so `prospectus` never fires for one.\n- `formTypes` (string[]): Registration families to include — `S-1`, `F-1`, `S-11`, `N-2` (applies to filing/amendment events; matched with the `/A` amendment suffix stripped)\n- `listingTypes` (string[]): `traditional`, `spac`, `direct_listing`\n- `countries` (string[]): Only `[\"US\"]` is accepted in v1; other values are rejected with 422\n- `maxLookbackDays` (number): Delivery lookback window in days\n\nRequired for LINKEDIN_POST and IPO_LIFECYCLE. Optional for JOB_POSTS when `prompt` is provided.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive webhook notifications when new signals are matched",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/market-signals"
},
"webhookSecret": {
"type": "string",
"description": "Optional secret for HMAC-SHA256 webhook signature verification",
"example": "whsec_abc123"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum number of signals to deliver per polling interval (1-10000, default 500). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000,
"default": 500,
"example": 100
},
"interval": {
"type": "string",
"description": "Polling interval for checking new signals",
"enum": [
"daily",
"weekly"
],
"default": "daily",
"example": "daily"
}
},
"additionalProperties": false
},
"UpdateMarketSignalSubscriptionRequest": {
"type": "object",
"description": "Partial update for a market signal subscription. All fields are optional; only provided\nfields are updated. The subscription `type` is immutable and cannot be changed.\n",
"properties": {
"name": {
"type": "string",
"description": "Updated display name",
"maxLength": 200,
"example": "Updated DevOps tracker"
},
"prompt": {
"type": "string",
"description": "Updated natural language prompt (JOB_POSTS only)"
},
"filters": {
"type": "object",
"description": "Updated subscription filters (must match the subscription's type schema)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Updated webhook URL",
"maxLength": 2048
},
"webhookSecret": {
"type": "string",
"description": "Updated webhook secret"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Updated maximum signals per interval (1-10000). Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"minimum": 1,
"maximum": 10000
},
"interval": {
"type": "string",
"description": "Updated polling interval. Changing this restarts the polling schedule.",
"enum": [
"daily",
"weekly"
]
}
},
"additionalProperties": false
},
"MarketSignalSubscriptionResponse": {
"type": "object",
"description": "A market signal subscription with its current configuration and status",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the subscription",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"organizationId": {
"type": "string",
"description": "Organization that owns this subscription",
"example": "org_123"
},
"type": {
"type": "string",
"description": "The subscription type (immutable after creation)",
"enum": [
"JOB_POSTS",
"LINKEDIN_POST",
"FUND_RAISED",
"RECENT_INVESTMENT",
"IPO",
"IPO_LIFECYCLE"
],
"example": "JOB_POSTS"
},
"name": {
"type": [
"string",
"null"
],
"description": "Display name for the subscription",
"example": "DevOps hiring tracker"
},
"status": {
"type": "string",
"description": "Current subscription status (derived from timestamps)",
"enum": [
"active",
"paused",
"deleted"
],
"example": "active"
},
"prompt": {
"type": [
"string",
"null"
],
"description": "Natural language prompt used for filter generation (if applicable)",
"example": "Find companies hiring machine learning engineers"
},
"filters": {
"type": "object",
"description": "The active filter configuration (structure varies by subscription type)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Webhook delivery URL",
"example": "https://myapp.com/webhooks/market-signals"
},
"intervalSignalLimit": {
"type": "integer",
"description": "Maximum signals per polling interval. Each run delivers at most this many. Over a billing cycle the monitor delivers at most this value times the number of runs its interval allows in the cycle, then waits for the next cycle. When runs overlap, for example a manual trigger during a scheduled run, the cycle total can exceed this by at most one run's limit per overlapping run. For ipo_stage_changed, ipo_rumoured, repo_engaged and tender_published, which Saber checks on its own schedule (every few minutes, or every hour for tender_published), the limit bounds each check rather than each interval, and the billing-cycle bound is the effective ceiling.",
"example": 500
},
"interval": {
"type": "string",
"description": "Polling interval",
"enum": [
"daily",
"weekly"
],
"example": "daily"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was created",
"example": "2024-06-15T10:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the subscription was last updated",
"example": "2024-06-15T10:00:00Z"
}
},
"required": [
"id",
"organizationId",
"type",
"status",
"filters",
"webhookUrl",
"intervalSignalLimit",
"interval",
"createdAt",
"updatedAt"
]
},
"MarketSignalSubscriptionListResponse": {
"type": "object",
"description": "Paginated list of market signal subscriptions",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum subscriptions per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"MarketSignalResponse": {
"type": "object",
"description": "A single market signal matched by a subscription",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal",
"example": "sig_001"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription that matched this signal",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"jobPostingId": {
"type": [
"string",
"null"
],
"description": "Job posting ID (for JOB_POSTS signals)",
"example": "jp_123"
},
"externalSignalId": {
"type": [
"string",
"null"
],
"description": "External identifier from the data source (e.g., LinkedIn post URN, EDGAR filing ID)",
"example": "li_post_456"
},
"payload": {
"type": [
"object",
"null"
],
"description": "Raw payload from the data source. Structure varies by subscription type:\n\n**JOB_POSTS**: Job posting details (title, company, location, description, URL, etc.)\n**LINKEDIN_POST**: Post content, author info, engagement metrics, company data\n**FUND_RAISED / RECENT_INVESTMENT / IPO**: Announcement details, amounts, companies involved. IPO signals also carry `formType` (the SEC form, e.g. `\"S-1\"` or `\"F-1\"`, for EDGAR-sourced filings).\n**IPO_LIFECYCLE**: Lean deterministic event — `eventType`, `companyName`, `source`, `sourceId`, `form`, `accessionNumber`, `filingUrl`, `ticker`, `exchange`, `listingType`, `country`, `occurredAt`, `detectedAt`, `sources[]`. Optional `companyDomain`: the registrant's website domain, found by Saber's company domain research. It is the only AI-derived field and is absent until the registrant is resolved, so an early event can arrive without it.\n\nThe registrant is identified by the PAIR `source` + `sourceId`, which is always present. `source` is one of `sec` or `occ` — it names the REGISTRANT's regulator, not the feed an observation came from. The wires that corroborate an event (GlobeNewswire, Business Wire, the Nasdaq calendar, DOL EFAST) appear in `sources[]`; they never appear in `source`.\n\n`cik` is CONDITIONAL and is omitted when the registrant has none. A national bank with no holding company registers its shares with the Office of the Comptroller of the Currency under 12 CFR part 16 instead of with the SEC, and is never assigned a CIK; for such an event `source` is `occ` and `sourceId` is the only registrant identifier. Note that an `occ` `sourceId` is currently a NORMALISED FORM OF THE BANK NAME (for example `american-savings-bank`), because the OCC Reading Room publishes no charter number — it is stable for a given label but two labels for one bank can produce two identifiers.\n",
"additionalProperties": true
},
"confidenceScore": {
"type": [
"number",
"null"
],
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "AI confidence score for the signal's relevance (0-1)",
"example": 0.92
},
"status": {
"type": "string",
"description": "Delivery status of the signal",
"enum": [
"pending",
"delivered",
"skipped"
],
"example": "delivered"
},
"publishedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the original content was published at the source",
"example": "2024-06-14T08:00:00Z"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the signal was matched and stored",
"example": "2024-06-15T10:30:00Z"
},
"deliveredAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the signal was delivered via webhook (null if not yet delivered)",
"example": "2024-06-15T10:31:00Z"
}
},
"required": [
"id",
"subscriptionId",
"status",
"createdAt"
]
},
"MarketSignalListResponse": {
"type": "object",
"description": "Paginated list of market signals for a subscription",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MarketSignalResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of signals for the subscription",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum signals per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of signals skipped",
"example": 0
}
},
"required": [
"items",
"total",
"limit",
"offset"
]
},
"ConnectorsListResponse": {
"type": "object",
"properties": {
"connectors": {
"type": "array",
"description": "List of connectors configured for the organization",
"items": {
"$ref": "#/components/schemas/Connector"
}
}
},
"required": [
"connectors"
]
},
"Connector": {
"type": "object",
"description": "A data integration connector and its current connection status",
"properties": {
"source": {
"type": "string",
"description": "The connector type identifier",
"enum": [
"salesNavigator",
"hubspotApp"
],
"example": "hubspotApp"
},
"status": {
"type": "string",
"description": "The current connection status of the connector",
"enum": [
"connected",
"disconnected",
"needs-authentication",
"ready-to-connect",
"error"
],
"example": "connected"
}
},
"required": [
"source",
"status"
]
},
"CreateContactSignalRequest": {
"type": "object",
"required": [
"contactProfileUrl",
"question"
],
"properties": {
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact profile URL to research (e.g., \"https://linkedin.com/in/johndoe\")",
"minLength": 1,
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"question": {
"type": "string",
"description": "The research question to ask about the contact",
"minLength": 1,
"maxLength": 2000,
"example": "What are this person's main professional interests?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType (same structure as company signals)",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n\nNote: Contact signals always require LinkedIn, so this setting is accepted but ignored for contact signals.\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"CreateSignalBatchRequest": {
"type": "object",
"required": [
"signals",
"domains"
],
"properties": {
"signals": {
"type": "array",
"description": "Array of signals to create. Each signal will be combined with each domain\nusing Cartesian product logic. You can either:\n- Provide inline signal configuration with `question` and optional fields (quick ad-hoc research)\n- Use `templateId` to reference a pre-created template (recommended for reusability)\n",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The research question. Required if templateId is not provided.\nIf templateId is provided, this overrides the template's question.\n",
"minLength": 1,
"maxLength": 2000,
"example": "What is their main product offering?"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "ID of the signal template to use. If provided, other fields (except question) use template settings.",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"answerType": {
"type": "string",
"description": "Expected answer format (only used for inline signals without templateId)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (only used when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Signal importance (only used for inline signals without templateId)",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for the answer (only used for inline signals without templateId)",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "URL to receive a webhook notification when the signal completes processing.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"forceRefresh": {
"type": "boolean",
"description": "When true, bypasses the signal cache and forces fresh processing.\nThis ensures the webhook is delivered even if a cached result exists.\n",
"default": false,
"example": false
}
}
}
},
"domains": {
"type": "array",
"description": "Array of company domains to research. Each domain will be combined with each signal template\nusing Cartesian product logic. The total resulting signals (len(signals) × len(domains)) must not exceed 100.\n",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"generateSummaryOnComplete": {
"type": "boolean",
"description": "Whether to automatically generate a summary when all signals in the batch are completed.\nWhen set to `true`, a summary will be automatically generated for each unique domain in the batch\nonce all signals for that domain have completed processing.\n",
"default": false,
"example": false
},
"async": {
"type": "boolean",
"description": "When `true`, enables async processing with a higher limit of 20,000 signals.\nReturns a `batchId` for tracking instead of inline results.\nThe response shape differs from sync mode — returns `{ batchId, submittedAt, totalSignals, status, async }`.\nUse `/v1/companies/signals/batches/status?domain=...` to poll for progress.\n",
"default": false,
"example": false
}
},
"additionalProperties": false
},
"SignalBatchResponse": {
"type": "object",
"properties": {
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was submitted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals created from Cartesian product (templates × domains)",
"minimum": 0,
"maximum": 100,
"example": 6
},
"accepted": {
"type": "integer",
"description": "Number of signals successfully scheduled for processing",
"minimum": 0,
"example": 5
},
"rejected": {
"type": "integer",
"description": "Number of signals that failed to be scheduled",
"minimum": 0,
"example": 1
},
"results": {
"type": "array",
"description": "Array of results for each signal in the batch, ordered by Cartesian product expansion",
"items": {
"$ref": "#/components/schemas/SignalResponse"
}
}
},
"required": [
"submittedAt",
"totalSignals",
"accepted",
"rejected",
"results"
]
},
"AsyncBatchResponse": {
"type": "object",
"description": "Response returned when `async: true` is set in the batch request",
"properties": {
"batchId": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the async batch. Use this to track progress.",
"example": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"submittedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the batch was accepted",
"example": "2024-01-15T10:30:00Z"
},
"totalSignals": {
"type": "integer",
"description": "Total number of signals that will be processed",
"minimum": 1,
"maximum": 20000,
"example": 15000
},
"status": {
"type": "string",
"description": "Status of the batch submission",
"enum": [
"accepted"
],
"example": "accepted"
},
"async": {
"type": "boolean",
"description": "Always `true` for async batch responses",
"example": true
}
},
"required": [
"batchId",
"submittedAt",
"totalSignals",
"status",
"async"
]
},
"CreateSignalRequest": {
"type": "object",
"required": [
"domain",
"question"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to research (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"question": {
"type": "string",
"description": "The research question to ask about the company",
"minLength": 1,
"maxLength": 2000,
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nThe AI will generate data conforming to this schema.\n\n**Supported JSON Schema features:**\n- `type`: object, array, string, number, integer, boolean, null\n- `properties`: Define object properties\n- `required`: List of required property names\n- `items`: Schema for array items\n- `enum`: Enumeration of allowed values\n- `description`: Property descriptions (helps AI generate better data)\n\n**Limitations:**\n- Maximum 100 total properties\n- Maximum 5 levels of nesting\n- `anyOf`, `oneOf`, `allOf` are not supported\n- `$ref` only supports self-references for recursive schemas\n",
"additionalProperties": true,
"example": {
"type": "object",
"properties": {
"pricingTiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
}
},
"hasFreeTier": {
"type": "boolean"
}
},
"required": [
"pricingTiers"
]
}
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes.\nWebhooks are only sent for freshly processed signals — cached results do not trigger webhooks.\nSet `forceRefresh: true` to bypass the cache and ensure webhook delivery.\n",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/signals"
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.\n\nFor boolean answerType:\n```json\n{\n \"yes\": \"good\",\n \"no\": \"disqualified\"\n}\n```\n\nFor number, percentage, or currency answerType:\n```json\n{\n \"ranges\": [\n { \"rangeStart\": 0, \"rangeEnd\": 100, \"answerValue\": \"neutral\" },\n { \"rangeStart\": 101, \"rangeEnd\": 1000, \"answerValue\": \"good\" }\n ]\n}\n```\n\nFor list answerType:\n```json\n{\n \"choices\": {\n \"salesforce\": { \"answerValue\": \"good\", \"label\": \"Salesforce\" },\n \"hubspot\": { \"answerValue\": \"neutral\", \"label\": \"HubSpot\" },\n \"none\": { \"answerValue\": \"disqualified\", \"label\": \"No CRM\" }\n }\n}\n```\n\nValid answerValue options (from worst to best):\n- disqualified: Answer indicates a deal-breaker or disqualifying factor\n- poor: Answer indicates a weak fit\n- neutral: Answer is acceptable but not ideal\n- good: Answer indicates a strong fit\n- excellent: Answer indicates an exceptional fit\n",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used.\nThe system always resolves to the latest active version of the template.\nOnly the domain parameter is required when using a template.\n",
"example": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"forceRefresh": {
"type": "boolean",
"description": "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.",
"default": false,
"example": false
},
"connectors": {
"type": "object",
"description": "Connector configuration for signal generation",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred",
"description": "Controls Sales Navigator usage during signal generation:\n- **required**: Fail request if Sales Navigator unavailable (rate limited/disconnected)\n- **preferred**: Attempt Sales Navigator, degrade gracefully if unavailable (default)\n- **off**: Do not attempt Sales Navigator\n",
"example": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"description": "Controls how strictly Saber verifies answers before responding.\n\n- **strict**: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.\n- **lenient**: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.\n",
"enum": [
"strict",
"lenient"
],
"default": "strict",
"example": "strict"
}
},
"additionalProperties": false
},
"BaseSignalResponse": {
"type": "object",
"description": "Base schema containing fields common to all signal types",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the signal (UUID format)",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"signalType": {
"type": "string",
"description": "Type of signal (company or contact)",
"enum": [
"COMPANY",
"CONTACT"
],
"example": "COMPANY"
},
"status": {
"type": "string",
"description": "Current processing status of the signal",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the enrichment-runs list's `entityId` filter matches on. Absent on contact signals and when resolution found no company.",
"example": "11111111-1111-1111-1111-111111111111"
},
"question": {
"type": "string",
"description": "The research question",
"example": "What is Acme Corp's main business focus?"
},
"answerType": {
"type": "string",
"description": "Answer type for this signal",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"open_text",
"url",
"list",
"json_schema",
"contacts",
"contact_posts"
],
"example": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this signal was created for your organization. If your organization already requested this signal within the cache window, the response reports that earlier request's creation time.",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"signalType",
"status",
"question",
"createdAt"
]
},
"CompanySignalResponse": {
"description": "Response for company signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"COMPANY"
],
"example": "COMPANY"
},
"domain": {
"type": "string",
"description": "The domain that was researched",
"example": "acme.com"
}
},
"required": [
"domain"
]
}
]
},
"ContactSignalResponse": {
"description": "Response for contact signals",
"allOf": [
{
"$ref": "#/components/schemas/BaseSignalResponse"
},
{
"type": "object",
"properties": {
"signalType": {
"type": "string",
"enum": [
"CONTACT"
],
"example": "CONTACT"
},
"contactProfileUrl": {
"type": "string",
"description": "LinkedIn profile URL",
"example": "https://linkedin.com/in/johndoe"
}
},
"required": [
"contactProfileUrl"
]
}
]
},
"EnrichmentRunCredits": {
"type": "object",
"description": "What this run cost the calling organization, from the credit ledger. `none` = served free (e.g. cache hit); `reserved` = charge pending completion; `charged` = confirmed; `released` = a reservation was returned (failed/expired run). Only present on run reads.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"reserved",
"charged",
"released",
"none"
]
},
"amount": {
"type": "number",
"description": "Positive credit amount of the debit."
},
"priceVersion": {
"type": "string",
"description": "Version of the price list that priced this debit."
}
}
},
"EnrichmentRunEntity": {
"type": "object",
"description": "The enriched entity (company or contact identifiers).",
"properties": {
"domain": {
"type": "string"
},
"companyId": {
"type": "string",
"description": "Resolved company id, when domain resolution succeeded — the value the runs list's `entityId` filter matches on."
},
"contactProfileUrl": {
"type": "string"
}
}
},
"EnrichmentTypesResponse": {
"type": "object",
"required": [
"results",
"total"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentType"
}
},
"total": {
"type": "integer",
"example": 6
}
}
},
"EnrichmentType": {
"type": "object",
"description": "One typed enrichment in the catalogue. `key` is the stable\ncustomer-facing identifier and, for company types, the route\nsegment of `POST /v1/companies/enrich/{key}`.\n",
"required": [
"object",
"key",
"entity",
"displayName",
"description",
"resultSchema",
"price",
"executionMode",
"deprecationState"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_type"
]
},
"key": {
"type": "string",
"example": "firmographics"
},
"entity": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"displayName": {
"type": "string",
"example": "Firmographics"
},
"description": {
"type": "string"
},
"resultSchema": {
"type": "object",
"description": "The OpenAPI component (in this document) the type's completed\nanswer PAYLOAD conforms to, and the current content revision of\nthat contract. WHERE that payload sits depends on `executionMode`\n— key off it:\n * `async` types: the component describes `result.jsonSchema`\n (the same `answer.jsonSchema` the op descriptions cite).\n Validate that sub-field, NOT the whole `result` envelope,\n which also carries status/sources/reasoning.\n * `sync` types (e.g. `work-email`): there is no `result`\n envelope — the component describes the WHOLE create-response\n body. Validate the response root against it.\n",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string",
"example": "FirmographicsSignalAnswer"
},
"version": {
"type": "integer",
"example": 3
}
}
},
"price": {
"type": "object",
"description": "The standard rate-card price. Per-organization overrides apply\nat charge time and are not reflected here.\n",
"required": [
"credits",
"version"
],
"properties": {
"credits": {
"type": "number",
"example": 0.6
},
"version": {
"type": "string",
"description": "The rate-card version the price was published from.",
"example": "2026-08-07"
}
}
},
"executionMode": {
"type": "string",
"enum": [
"async",
"sync"
],
"description": "`async` — `202` + `Location: /v1/enrichment-runs/{runId}`, with\n`200` inline on a cache serve. `sync` — the answer arrives on\nthe create response.\n"
},
"deprecationState": {
"type": "string",
"description": "`active` today for every entry. Treat unknown values as\nforward-compatible additions (sunset states arrive with the\ndeprecation plan).\n",
"example": "active"
}
}
},
"EnrichmentRun": {
"type": "object",
"description": "One enrichment execution. The same record as a custom signal, presented with an explicit result contract; ids are interchangeable with signal ids during the transition.",
"required": [
"id",
"object",
"entityType",
"type",
"status",
"entity",
"result",
"resultSchemaVersion",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"enrichment_run"
]
},
"entityType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"type": {
"type": "string",
"description": "Enrichment type: `custom` for custom questions, or a prebuilt enrichment type (`firmographics`, `funding`, `mna`, `open-jobs`, `tech-stack`) — regardless of whether the run was created on this surface or the legacy signals surface."
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed"
]
},
"entity": {
"$ref": "#/components/schemas/EnrichmentRunEntity"
},
"question": {
"type": "string"
},
"signalTemplateId": {
"type": "string"
},
"signalTemplateVersionId": {
"type": "string"
},
"answerType": {
"type": "string"
},
"result": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The typed answer; null until the run completes."
},
"resultSchemaVersion": {
"type": "string"
},
"reasoning": {
"type": "string"
},
"confidence": {
"type": "number"
},
"sources": {
"type": "array",
"items": {}
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string"
},
"changeContext": {
"type": "object",
"additionalProperties": true
},
"credits": {
"$ref": "#/components/schemas/EnrichmentRunCredits"
},
"error": {
"type": "string"
},
"errorCode": {
"type": "string"
},
"retryable": {
"type": "boolean",
"description": "Present on failed runs only."
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
},
"EnrichmentRunBatchRequest": {
"type": "object",
"description": "Either `question` or `signalTemplateId` must be provided; the shared config applies to every target.",
"required": [
"targets"
],
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"description": "Company domains; one run per target."
},
"question": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"webhookUrl": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"signalTemplateId": {
"type": "string",
"format": "uuid"
},
"forceRefresh": {
"type": "boolean"
},
"connectors": {
"type": "object",
"description": "Connector configuration applied to every run in the batch.",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"default": "preferred"
}
}
}
}
},
"verificationMode": {
"type": "string",
"enum": [
"strict",
"lenient"
]
},
"generateSummaryOnComplete": {
"type": "boolean"
}
}
},
"EnrichmentRunBatchResponse": {
"type": "object",
"required": [
"object",
"accepted",
"rejected",
"runs"
],
"properties": {
"object": {
"type": "string",
"enum": [
"enrichment_run_batch"
]
},
"accepted": {
"type": "integer"
},
"rejected": {
"type": "integer"
},
"runs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
}
}
},
"EnrichmentRunListResponse": {
"type": "object",
"required": [
"results",
"total",
"limit",
"offset",
"count"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnrichmentRun"
}
},
"total": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"offset": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
},
"SignalResponse": {
"description": "Polymorphic signal response (company or contact)",
"oneOf": [
{
"$ref": "#/components/schemas/CompanySignalResponse"
},
{
"$ref": "#/components/schemas/ContactSignalResponse"
}
],
"discriminator": {
"propertyName": "signalType",
"mapping": {
"COMPANY": "#/components/schemas/CompanySignalResponse",
"CONTACT": "#/components/schemas/ContactSignalResponse"
}
}
},
"SignalDetailResponse": {
"allOf": [
{
"$ref": "#/components/schemas/SignalResponse"
},
{
"$ref": "#/components/schemas/SignalDetailFields"
}
]
},
"SignalDetailFields": {
"type": "object",
"properties": {
"completedAt": {
"type": "string",
"format": "date-time",
"description": "When processing completed for your organization (only present when status is completed). If your organization already requested this signal within the cache window, the response reports that earlier request's completion time.",
"example": "2024-01-15T10:32:15Z"
},
"answer": {
"oneOf": [
{
"$ref": "#/components/schemas/Answer"
},
{
"type": "null"
}
],
"description": "The AI-generated answer (null when status is not completed)"
},
"answerStatus": {
"type": "string",
"enum": [
"answered",
"not_found",
"insufficient_evidence",
"conflicting_evidence"
],
"description": "Semantic disposition of a completed answer. `answered` includes supported negative values such as false, empty lists, or structured none-found results. Historical null answers may omit this field."
},
"reasoning": {
"type": "string",
"description": "Detailed reasoning for the answer (only present when status is completed)",
"example": "Based on the company's website and public information, Acme Corp specializes in enterprise software."
},
"confidence": {
"type": "number",
"format": "float",
"minimum": 0,
"maximum": 1,
"description": "Confidence score for the answer (0-1, only present when status is completed)",
"example": 0.95
},
"sources": {
"type": "array",
"description": "Sources used to generate the answer (only present when status is completed, maximum 20 sources)",
"maxItems": 20,
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"example": "Leading provider of enterprise software..."
}
}
}
},
"metadata": {
"type": "object",
"description": "Additional processing metadata. Runtime metadata may be present before completion.",
"additionalProperties": true,
"properties": {
"companyName": {
"type": "string",
"description": "Company name (for company signals)",
"example": "Acme Corp"
},
"processingTimeMs": {
"type": "number",
"description": "Processing time in milliseconds",
"example": 45200
},
"connectors": {
"type": "object",
"description": "Connector status information",
"properties": {
"salesNavigator": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"degraded"
],
"description": "Connector status for this request",
"example": "ok"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "Reason for degraded status (only present when status is degraded)",
"example": "rate_limited"
}
}
}
}
},
"linkedInContext": {
"$ref": "#/components/schemas/LinkedInContext"
}
},
"example": {
"companyName": "Acme Corp",
"processingTimeMs": 45200,
"connectors": {
"salesNavigator": {
"status": "ok"
}
}
}
},
"error": {
"type": "string",
"description": "Error message (only present when status is failed)",
"example": "LinkedIn data is currently unavailable due to rate limits. Please try again later."
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (only present when status is failed)",
"enum": [
"LINKEDIN_RATE_LIMIT_EXCEEDED",
"LINKEDIN_CONNECTOR_REQUIRED",
"LINKEDIN_POLICY_UNAVAILABLE",
"CONNECTOR_UNAVAILABLE",
"VALIDATION_ERROR"
],
"example": "LINKEDIN_RATE_LIMIT_EXCEEDED"
},
"errorAction": {
"type": "string",
"description": "Suggested action to resolve the error (only present when status is failed)",
"example": "You can: (1) Set connectors.salesNavigator.enabled=\"off\" to skip LinkedIn data, (2) Add more LinkedIn connectors, or (3) Try again in 24 hours when rate limits reset."
},
"retryable": {
"type": "boolean",
"description": "Whether the failure is worth retrying (only present when status is failed). `true` = transient (e.g. upstream sources unavailable); `false` = non-retryable (e.g. validation, rate limit).",
"example": true
},
"changeContext": {
"$ref": "#/components/schemas/ChangeContext",
"description": "Information about changes between signal versions (only present when status is completed and a previous version exists)"
},
"verificationMode": {
"type": "string",
"description": "Verification mode used for signal generation",
"enum": [
"strict",
"lenient"
],
"example": "strict"
},
"company": {
"$ref": "#/components/schemas/CompanyFirmographics",
"description": "Company firmographic data (only present in webhook payloads for company signals)"
}
}
},
"CompanyFirmographics": {
"type": "object",
"description": "Company firmographic and LinkedIn enrichment data included in webhook payloads",
"properties": {
"id": {
"type": "string",
"description": "Company ID",
"example": "comp_abc123"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "Software"
},
"website": {
"type": "string",
"description": "Company website URL",
"example": "https://acme.com"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type (e.g. private, public)",
"example": "private"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City of headquarters",
"example": "San Francisco"
},
"state": {
"type": "string",
"description": "State or region of headquarters",
"example": "California"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"handle": {
"type": "string",
"description": "Company handle/slug",
"example": "acme-corp"
},
"linkedInId": {
"type": "integer",
"description": "LinkedIn numeric company ID",
"example": 12345678
},
"linkedInUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn company page URL",
"example": "https://www.linkedin.com/company/acme-corp"
},
"linkedInFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers",
"example": 5200
},
"employeeCount": {
"type": "integer",
"description": "Approximate employee count from LinkedIn",
"example": 150
},
"description": {
"type": "string",
"description": "Company description",
"example": "Acme Corp is a leading provider of enterprise software solutions."
},
"logoUrl": {
"type": "string",
"format": "uri",
"description": "Company logo URL",
"example": "https://media.licdn.com/dms/image/acme-logo.png"
}
}
},
"Answer": {
"description": "Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n",
"oneOf": [
{
"type": "object",
"required": [
"type",
"openText"
],
"properties": {
"type": {
"type": "string",
"enum": [
"open_text"
],
"description": "The answer type discriminator"
},
"openText": {
"$ref": "#/components/schemas/OpenTextAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"number"
],
"properties": {
"type": {
"type": "string",
"enum": [
"number"
],
"description": "The answer type discriminator"
},
"number": {
"$ref": "#/components/schemas/NumberAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"boolean"
],
"properties": {
"type": {
"type": "string",
"enum": [
"boolean"
],
"description": "The answer type discriminator"
},
"boolean": {
"$ref": "#/components/schemas/BooleanAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"list"
],
"properties": {
"type": {
"type": "string",
"enum": [
"list"
],
"description": "The answer type discriminator"
},
"list": {
"$ref": "#/components/schemas/ListAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"percentage"
],
"properties": {
"type": {
"type": "string",
"enum": [
"percentage"
],
"description": "The answer type discriminator"
},
"percentage": {
"$ref": "#/components/schemas/PercentageAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"currency"
],
"properties": {
"type": {
"type": "string",
"enum": [
"currency"
],
"description": "The answer type discriminator"
},
"currency": {
"$ref": "#/components/schemas/CurrencyAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"url"
],
"properties": {
"type": {
"type": "string",
"enum": [
"url"
],
"description": "The answer type discriminator"
},
"url": {
"$ref": "#/components/schemas/URLAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contacts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contacts"
],
"description": "The answer type discriminator"
},
"contacts": {
"$ref": "#/components/schemas/ContactsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactPosts"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_posts"
],
"description": "The answer type discriminator"
},
"contactPosts": {
"$ref": "#/components/schemas/ContactPostsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"contactEngagements"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contact_engagements"
],
"description": "The answer type discriminator"
},
"contactEngagements": {
"$ref": "#/components/schemas/ContactEngagementsAnswer"
}
}
},
{
"type": "object",
"required": [
"type",
"jsonSchema"
],
"properties": {
"type": {
"type": "string",
"enum": [
"json_schema"
],
"description": "The answer type discriminator"
},
"jsonSchema": {
"$ref": "#/components/schemas/JSONSchemaAnswer"
}
}
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"open_text": "#/components/schemas/OpenTextAnswer",
"number": "#/components/schemas/NumberAnswer",
"boolean": "#/components/schemas/BooleanAnswer",
"list": "#/components/schemas/ListAnswer",
"percentage": "#/components/schemas/PercentageAnswer",
"currency": "#/components/schemas/CurrencyAnswer",
"url": "#/components/schemas/URLAnswer",
"contacts": "#/components/schemas/ContactsAnswer",
"contact_posts": "#/components/schemas/ContactPostsAnswer",
"contact_engagements": "#/components/schemas/ContactEngagementsAnswer",
"json_schema": "#/components/schemas/JSONSchemaAnswer"
}
}
},
"OpenTextAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"description": "Free-form text response",
"example": "Acme Corp is a leading provider of enterprise software solutions"
}
}
},
"NumberAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "number",
"description": "Numeric value",
"example": 1250000000
}
}
},
"BooleanAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "boolean",
"description": "True or false value",
"example": true
}
}
},
"ListAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of string items (maximum 500 items)",
"maxItems": 500,
"items": {
"type": "string"
},
"example": [
"Enterprise Software",
"CRM Solutions",
"Business Automation"
]
}
}
},
"PercentageAnswer": {
"type": "object",
"required": [
"value",
"unit"
],
"properties": {
"value": {
"type": "number",
"description": "Percentage value",
"example": 85.5
},
"unit": {
"type": "string",
"description": "Unit symbol (typically \"%\")",
"example": "%"
}
}
},
"CurrencyAnswer": {
"type": "object",
"required": [
"value",
"currency"
],
"properties": {
"value": {
"type": "number",
"description": "Monetary amount",
"example": 1250000000
},
"currency": {
"type": "string",
"description": "ISO 4217 currency code",
"example": "USD"
}
}
},
"URLAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string",
"format": "uri",
"description": "Valid URL string",
"example": "https://www.example.com/product"
}
}
},
"JSONSchemaAnswer": {
"type": "object",
"description": "The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)",
"additionalProperties": true,
"example": {
"pricingTiers": [
{
"name": "Starter",
"price": 0,
"features": [
"5 users",
"Basic support"
]
},
{
"name": "Pro",
"price": 99,
"features": [
"Unlimited users",
"Priority support"
]
}
],
"hasFreeTier": true
}
},
"ContactsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of contact objects with detailed information (maximum 100 contacts)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/Contact"
}
}
}
},
"Contact": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact"
},
"lastName": {
"type": "string",
"description": "Last name of the contact"
},
"fullName": {
"type": "string",
"description": "Full name of the contact"
},
"headline": {
"type": "string",
"description": "Professional headline"
},
"summary": {
"type": "string",
"description": "Professional summary"
},
"positions": {
"type": "array",
"description": "Array of current and past positions",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels"
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL"
},
"linkedInProfileUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn profile URL"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Avatar/profile picture URL"
},
"location": {
"type": "string",
"description": "Location of the contact"
},
"followerCount": {
"type": "integer",
"description": "Number of followers on LinkedIn",
"example": 10749
},
"education": {
"type": "array",
"description": "Array of education entries",
"items": {
"$ref": "#/components/schemas/Education"
}
}
}
},
"ContactPosition": {
"type": "object",
"description": "One position on a contact or contact-search result. Contact search reads Sales Navigator, so its positions can carry both tenure figures and the move classification derived from them. Other contact sources can carry a single collapsed tenure instead. All tenure and classification fields are optional. `tenureAtPosition` is time in this role. `tenureAtCompany` is total time at this employer across roles.",
"required": [
"companyName",
"title"
],
"properties": {
"companyName": {
"type": "string",
"description": "Name of the company"
},
"companyUrl": {
"type": "string",
"format": "uri",
"description": "Company LinkedIn URL"
},
"title": {
"type": "string",
"description": "Job title/role"
},
"tenure": {
"$ref": "#/components/schemas/Tenure",
"description": "Time held, as a single collapsed value."
},
"tenureInMonths": {
"type": "integer",
"description": "Total tenure in months (computed from tenure)"
},
"tenureAtPosition": {
"description": "Time in this current role.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"tenureAtCompany": {
"description": "Total time at this employer across current and earlier roles.",
"allOf": [
{
"$ref": "#/components/schemas/Tenure"
}
]
},
"moveType": {
"type": "string",
"enum": [
"moved_internally",
"joined_new_employer",
"no_recent_move",
"unknown"
],
"description": "Whether this position looks like a recent move, inferred from the two tenure values. `moved_internally` is a new role at an employer the person already worked for. `joined_new_employer` means the tenure values do not show earlier time at this employer; it can also be a missed internal move. `no_recent_move` means the role is older than the recency boundary. `unknown` means the tenure data cannot support a direction. This is our inference, not a Sales Navigator field, and it is not a promotion claim.",
"example": "moved_internally"
},
"location": {
"type": "string",
"description": "Location of the position"
},
"description": {
"type": "string",
"description": "Description of the role"
},
"startedOn": {
"$ref": "#/components/schemas/ContactDate"
},
"endedOn": {
"description": "End date of position (null if currently employed)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactDate": {
"type": "object",
"required": [
"year"
],
"properties": {
"year": {
"type": "integer",
"description": "Year",
"example": 2021
},
"month": {
"type": "integer",
"description": "Month (1-12)",
"example": 6
}
}
},
"Education": {
"type": "object",
"required": [
"schoolName"
],
"properties": {
"schoolName": {
"type": "string",
"description": "Name of the educational institution"
},
"schoolUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn URL of the school"
},
"degree": {
"type": "string",
"description": "Degree obtained (e.g., \"Bachelor of Science\")"
},
"fieldOfStudy": {
"type": "string",
"description": "Field of study (e.g., \"Computer Science\")"
},
"startDate": {
"$ref": "#/components/schemas/ContactDate"
},
"endDate": {
"description": "End date of education (null if currently enrolled)",
"oneOf": [
{
"$ref": "#/components/schemas/ContactDate"
},
{
"type": "null"
}
]
}
}
},
"ContactPostsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of post objects with detailed information",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"ContactEngagementsAnswer": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "array",
"description": "Array of engagement objects (maximum 100 engagements)",
"maxItems": 100,
"items": {
"$ref": "#/components/schemas/ContactEngagement"
}
}
}
},
"ContactEngagement": {
"type": "object",
"required": [
"postUrl",
"engagementType"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "URL of the LinkedIn post where the engagement occurred"
},
"postAuthor": {
"$ref": "#/components/schemas/PostAuthor",
"description": "Author of the original post"
},
"postText": {
"type": "string",
"description": "Snippet of post content"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 post publication timestamp"
},
"engagementType": {
"type": "string",
"enum": [
"comment",
"reaction"
],
"description": "Type of engagement"
},
"engagementDate": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 engagement timestamp"
},
"commentText": {
"type": "string",
"description": "Text of comment (if engagementType is comment)"
},
"reactionText": {
"type": "string",
"description": "Reaction text extracted from LinkedIn header (e.g., \"John celebrates this\", \"John likes this\", \"John finds this insightful\")"
}
}
},
"Post": {
"type": "object",
"required": [
"postUrl"
],
"properties": {
"postUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn post URL (identity key for tracking changes)"
},
"postText": {
"type": "string",
"description": "Text content of the post"
},
"activityUrn": {
"type": "string",
"description": "LinkedIn activity URN"
},
"publishedAt": {
"type": "string",
"description": "Publication date"
},
"postType": {
"type": "string",
"enum": [
"original",
"shared",
"repost"
],
"description": "Type of post"
},
"author": {
"$ref": "#/components/schemas/PostAuthor"
},
"engagement": {
"$ref": "#/components/schemas/PostEngagement"
},
"subject": {
"type": "string",
"description": "AI-generated one-sentence summary of the post"
}
}
},
"PostAuthor": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Author's name"
},
"headline": {
"type": "string",
"description": "Author's professional headline"
},
"profileUrl": {
"type": "string",
"format": "uri",
"description": "Author's LinkedIn profile URL"
}
}
},
"PostEngagement": {
"type": "object",
"properties": {
"reactionCount": {
"type": "integer",
"description": "Number of reactions"
},
"commentCount": {
"type": "integer",
"description": "Number of comments"
},
"viewCount": {
"type": "integer",
"description": "Number of views"
},
"topReactionTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Most common reaction types"
}
}
},
"EngagementGrowth": {
"type": "object",
"description": "Engagement growth metrics computed during diff. Absolute deltas are always present\nwhen the metric exists on the current post. Growth percentages are only present when\nthe previous count was > 0 (undefined for zero-baseline virality detection).\n",
"properties": {
"reactionDelta": {
"type": "integer",
"description": "Absolute change in reaction count since last run"
},
"commentDelta": {
"type": "integer",
"description": "Absolute change in comment count since last run"
},
"viewDelta": {
"type": "integer",
"description": "Absolute change in view count since last run"
},
"reactionGrowthPct": {
"type": "number",
"description": "Reaction growth percentage since last run (omitted when previous was 0)"
},
"commentGrowthPct": {
"type": "number",
"description": "Comment growth percentage since last run (omitted when previous was 0)"
},
"viewGrowthPct": {
"type": "number",
"description": "View growth percentage since last run (omitted when previous was 0)"
}
}
},
"CreateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Mid-size US SaaS companies"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"UpdateCompanyListRequest": {
"type": "object",
"required": [
"name",
"filter"
],
"properties": {
"name": {
"type": "string",
"description": "Updated name for the list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "Updated list name"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"HubSpotPropertyFilter": {
"type": "object",
"description": "A single HubSpot property filter used to select companies for import",
"required": [
"propertyName",
"operator"
],
"properties": {
"propertyName": {
"type": "string",
"description": "The HubSpot company property to filter on (e.g. `industry`, `city`, `hs_lead_status`)",
"example": "industry"
},
"operator": {
"type": "string",
"description": "Comparison operator. Value-free operators (`HAS_PROPERTY`, `NOT_HAS_PROPERTY`)\ndo not require a `value` field. All others do.\n",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"HAS_PROPERTY",
"NOT_HAS_PROPERTY",
"CONTAINS_TOKEN",
"NOT_CONTAINS_TOKEN"
],
"example": "EQ"
},
"value": {
"type": "string",
"description": "The value to compare against. Required for all operators except `HAS_PROPERTY` and `NOT_HAS_PROPERTY`.",
"example": "Technology"
}
}
},
"ImportCompanyListSource": {
"type": "object",
"description": "Describes the external source to import companies from",
"required": [
"type",
"filter"
],
"properties": {
"type": {
"type": "string",
"description": "Import source type. Currently only `hubspot` is supported.",
"enum": [
"hubspot"
],
"example": "hubspot"
},
"filter": {
"$ref": "#/components/schemas/HubSpotPropertyFilter"
}
}
},
"ImportCompanyListRequest": {
"type": "object",
"required": [
"name",
"source"
],
"properties": {
"name": {
"type": "string",
"description": "A human-readable name for the imported list (1–200 characters)",
"minLength": 1,
"maxLength": 200,
"example": "HubSpot — Technology companies"
},
"source": {
"$ref": "#/components/schemas/ImportCompanyListSource"
}
}
},
"ImportCompanyListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CompanyListResponse"
},
{
"type": "object",
"properties": {
"unresolvedDomains": {
"type": "array",
"description": "Domains from the HubSpot result whose stub records could not be\nupserted. These domains are excluded from the list filter.\nPresent only when one or more upserts fail; omitted on full success.\n",
"items": {
"type": "string"
},
"example": [
"broken-domain.io"
]
}
}
}
]
},
"CompanyListResponse": {
"type": "object",
"description": "A saved company list with its filter definition",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the list"
},
"name": {
"type": "string",
"description": "Human-readable list name",
"example": "Mid-size US SaaS companies"
},
"prompt": {
"type": "string",
"description": "Optional prompt used to generate the filter"
},
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the list was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the list was last updated"
}
},
"required": [
"id",
"name",
"filter",
"createdAt"
]
},
"CompanyListsResponse": {
"type": "object",
"description": "Paginated list of company lists",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of lists",
"example": 5
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CompanyListCompaniesResponse": {
"type": "object",
"description": "Paginated list of companies in a company list",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
}
},
"total": {
"type": "integer",
"description": "Total number of companies in the list",
"example": 150
},
"limit": {
"type": "integer",
"description": "Maximum results per page",
"example": 25
},
"offset": {
"type": "integer",
"description": "Number of results skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether more results are available",
"example": true
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"ExportCompanyListRequest": {
"type": "object",
"description": "Optional parameters for the company list CSV export.",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string",
"enum": [
"industry",
"size",
"founded",
"city",
"state",
"country_code"
]
},
"description": "Optional firmographic columns to include after the fixed `name` and\n`website` columns, in the order specified. Unknown values return 422.\n",
"example": [
"industry",
"size",
"country_code"
]
},
"signalTemplateIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "IDs of signal templates whose subscriptions should be included as\ncolumns. Two columns are added per subscription:\n`{signal_name}_answer` and `{signal_name}_confidence`. If absent\nor empty, no signal columns are added.\n",
"example": [
"123e4567-e89b-12d3-a456-426614174000"
]
}
}
},
"CompanySearchRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"$ref": "#/components/schemas/CompanyListFilters"
}
}
},
"CompanyListFilters": {
"type": "object",
"description": "Criteria for filtering companies",
"properties": {
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company domains"
},
"names": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company names"
},
"handles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company handles"
},
"websites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company websites"
},
"industries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by industry names",
"example": [
"software development",
"technology, information and internet"
]
},
"sizes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company size ranges",
"example": [
"51-200",
"201-500"
]
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by company types"
},
"founded": {
"type": "object",
"description": "Filter by founded year range",
"properties": {
"before": {
"type": "integer",
"description": "Founded before this year"
},
"after": {
"type": "integer",
"description": "Founded after this year"
}
}
},
"location": {
"type": "object",
"description": "Filter by geographic location",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by city names"
},
"states": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by state/province names"
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by ISO country codes",
"example": [
"US",
"GB"
]
}
}
},
"exclude": {
"type": "object",
"description": "Exclusion filters (companies matching these are removed)",
"properties": {
"industries": {
"type": "array",
"items": {
"type": "string"
}
},
"sizes": {
"type": "array",
"items": {
"type": "string"
}
},
"types": {
"type": "array",
"items": {
"type": "string"
}
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"location": {
"type": "object",
"properties": {
"cities": {
"type": "array",
"items": {
"type": "string"
}
},
"states": {
"type": "array",
"items": {
"type": "string"
}
},
"countryCodes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"questionIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by signal question IDs"
},
"technologies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter by technology slugs (companies must use at least one of the specified technologies)",
"example": [
"stripe",
"hubspot"
]
}
}
},
"CompanySearchResponse": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanyListCompany"
},
"description": "Up to 50 companies matching the filter"
},
"total": {
"type": "integer",
"description": "Total number of companies matching the filter",
"example": 1234
}
},
"required": [
"companies",
"total"
]
},
"CompanyListCompany": {
"type": "object",
"description": "A company matching the search filter",
"properties": {
"id": {
"type": "string",
"description": "Unique company identifier"
},
"domain": {
"type": "string",
"description": "Company domain",
"example": "example.com"
},
"name": {
"type": "string",
"description": "Company name",
"example": "Example Inc"
},
"handle": {
"type": "string",
"description": "Company handle"
},
"website": {
"type": "string",
"description": "Company website URL"
},
"industry": {
"type": "string",
"description": "Industry classification",
"example": "software development"
},
"size": {
"type": "string",
"description": "Company size range",
"example": "51-200"
},
"type": {
"type": "string",
"description": "Company type"
},
"founded": {
"type": "integer",
"description": "Year the company was founded",
"example": 2015
},
"city": {
"type": "string",
"description": "City name"
},
"state": {
"type": "string",
"description": "State/province name"
},
"countryCode": {
"type": "string",
"description": "ISO country code",
"example": "US"
},
"enrichedData": {
"type": "object",
"description": "LinkedIn enrichment data",
"properties": {
"liId": {
"type": "integer",
"description": "LinkedIn company ID"
},
"liFollowers": {
"type": "integer",
"description": "Number of LinkedIn followers"
},
"liFoundEmployees": {
"type": "integer",
"description": "Number of employees found on LinkedIn"
}
}
}
},
"required": [
"id",
"domain",
"name"
]
},
"ErrorResponse": {
"type": "object",
"description": "Standard error envelope returned by every endpoint that flows through the global error handler. Note: the global rate-limit middleware (HTTP 429 from per-API-key throttling) returns a different, flat shape — see `RateLimitErrorResponse`.\n",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"type",
"code",
"requestId"
],
"properties": {
"type": {
"type": "string",
"description": "Error category. Maps 1:1 to HTTP status (e.g. VALIDATION → 422, UNAUTHORIZED → 401).",
"enum": [
"BAD_REQUEST",
"VALIDATION",
"UNPROCESSABLE_ENTITY",
"NOT_FOUND",
"CONFLICT",
"UNAUTHORIZED",
"FORBIDDEN",
"PAYMENT_REQUIRED",
"PAYLOAD_TOO_LARGE",
"TOO_MANY_REQUESTS",
"INTERNAL",
"EXTERNAL",
"TIMEOUT"
],
"example": "VALIDATION"
},
"code": {
"type": "string",
"description": "Stable machine-readable error code. Safe to switch on in client code.",
"example": "VALIDATION_ERROR"
},
"message": {
"type": "string",
"description": "Human-readable error message. Wording may change; do not match against this string.",
"example": "validation failed for 'departments': invalid value: Engineering Ops"
},
"errorCode": {
"type": "string",
"description": "Optional public error code propagated from a downstream service (e.g. NestJS, LinkedIn)."
},
"errorAction": {
"type": "string",
"description": "Optional user action guidance (e.g. \"reconnect Sales Navigator\")."
},
"details": {
"type": "object",
"additionalProperties": true,
"description": "Optional structured fields forwarded from a downstream service. Shape depends on the source."
},
"requestId": {
"type": "string",
"format": "uuid",
"description": "Correlation ID for this request. Include when reporting issues.",
"example": "e69ed773-2674-4dca-be61-886d6698f360"
},
"fields": {
"type": "array",
"description": "Per-field validation errors (populated when `type` is `VALIDATION` and the cause is a struct-tag validator).",
"items": {
"type": "object",
"required": [
"field",
"message"
],
"properties": {
"field": {
"type": "string",
"example": "domain"
},
"message": {
"type": "string",
"example": "'domain' must be a valid hostname (e.g., 'example.com') without protocol (https://) or path"
}
}
}
},
"debug": {
"type": "object",
"description": "Debug information. Only present in development environments.",
"properties": {
"cause": {
"type": "string"
}
}
}
}
}
}
},
"RateLimitErrorResponse": {
"type": "object",
"description": "Flat error envelope returned only by the global per-API-key rate-limit middleware (HTTP 429). All other errors — including 429s from per-route rate limiters — use the standard `ErrorResponse` envelope.\n",
"required": [
"statusCode",
"error",
"message"
],
"properties": {
"statusCode": {
"type": "integer",
"example": 429
},
"error": {
"type": "string",
"example": "Too Many Requests"
},
"message": {
"type": "string",
"example": "Rate limit exceeded"
},
"retryAfter": {
"type": "integer",
"description": "Seconds until the next request will be admitted.",
"example": 45
}
}
},
"ConnectorUnavailableError": {
"type": "object",
"description": "Error response when a required connector is unavailable",
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"connector",
"reason"
],
"properties": {
"code": {
"type": "string",
"enum": [
"CONNECTOR_UNAVAILABLE"
],
"description": "Machine-readable error code",
"example": "CONNECTOR_UNAVAILABLE"
},
"message": {
"type": "string",
"description": "Human-readable error message",
"example": "Sales Navigator is required but currently unavailable."
},
"connector": {
"type": "string",
"enum": [
"salesNavigator"
],
"description": "The connector that is unavailable",
"example": "salesNavigator"
},
"reason": {
"type": "string",
"enum": [
"rate_limited",
"disconnected",
"policy_unavailable",
"scheduler_unavailable"
],
"description": "The reason the connector is unavailable",
"example": "rate_limited"
},
"retry_after_seconds": {
"type": "integer",
"description": "Seconds to wait before retrying (present for rate_limited)",
"example": 300
}
}
}
},
"required": [
"error"
]
},
"CreateContactResearchRequest": {
"type": "object",
"required": [
"firstName",
"lastName",
"companyName",
"companyDomain"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"minLength": 1,
"maxLength": 100,
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Name of the contact's company",
"minLength": 1,
"maxLength": 200,
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
},
"contactProfileUrl": {
"type": "string",
"format": "uri",
"description": "Contact profile URL of the contact (e.g., LinkedIn or other professional profile, optional)",
"maxLength": 500,
"example": "https://linkedin.com/in/johndoe"
},
"linkedInSalesNavigatorUrl": {
"type": "string",
"format": "uri",
"description": "LinkedIn Sales Navigator profile URL of the contact (optional)",
"maxLength": 500,
"example": "https://www.linkedin.com/sales/lead/ACwAAABcD..."
},
"jobTitle": {
"type": "string",
"description": "Job title of the contact (optional)",
"minLength": 1,
"maxLength": 200,
"example": "VP of Sales"
},
"webhookUrl": {
"type": "string",
"format": "uri",
"description": "Optional webhook URL to receive notifications when processing completes",
"maxLength": 2048,
"example": "https://myapp.com/webhooks/contact-research"
}
},
"additionalProperties": false
},
"ContactResearchResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "processing"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"createdAt"
]
},
"ContactResearchDetailResponse": {
"allOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact research request",
"example": "cr_abc123def456"
},
"status": {
"type": "string",
"description": "Current processing status of the contact research",
"enum": [
"processing",
"completed",
"failed"
],
"example": "completed"
},
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"companyName": {
"type": "string",
"description": "Company name",
"example": "Acme Corp"
},
"companyDomain": {
"type": "string",
"description": "Company domain",
"example": "acme.com"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research request was created",
"example": "2024-01-15T10:30:00Z"
}
},
"required": [
"id",
"status",
"firstName",
"lastName",
"companyName",
"companyDomain",
"createdAt"
]
},
{
"type": "object",
"properties": {
"contactProfileUrl": {
"type": "string",
"description": "Contact profile URL if provided (e.g., LinkedIn or other professional profile)",
"example": "https://linkedin.com/in/johndoe"
},
"jobTitle": {
"type": "string",
"description": "Job title - prioritizes the title found during research (profile.currentRole), falls back to the title provided in the request if research didn't find one",
"example": "VP of Sales"
},
"profile": {
"type": "object",
"description": "AI-generated profile analysis (only present when status is \"completed\")",
"properties": {
"summary": {
"type": "string",
"description": "Professional summary"
},
"currentRole": {
"type": "string",
"description": "Current job title"
},
"currentCompany": {
"type": "string",
"description": "Current company"
},
"location": {
"type": "string",
"description": "Professional location"
},
"experience": {
"type": "array",
"description": "Work experience history",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"company": {
"type": "string"
},
"duration": {
"type": "string"
}
}
}
}
}
},
"communicationStyle": {
"type": "object",
"description": "Preferred communication style (only present when status is \"completed\")",
"properties": {
"preference": {
"type": "string"
}
}
},
"interests": {
"type": "array",
"description": "Professional interests and topics (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"challenges": {
"type": "array",
"description": "Potential challenges or pain points (only present when status is \"completed\")",
"items": {
"type": "string"
}
},
"linkedInActivity": {
"type": "object",
"description": "Recent LinkedIn activity analysis (only present when status is \"completed\")",
"properties": {
"postingFrequency": {
"type": "string"
},
"engagementLevel": {
"type": "string"
},
"lastActivityDate": {
"type": "string",
"format": "date-time"
}
}
},
"error": {
"type": "string",
"description": "Error message if status is \"failed\"",
"example": "Could not retrieve LinkedIn profile data"
},
"completedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the research completed (success or failure)",
"example": "2024-01-15T10:35:00Z"
}
}
}
]
},
"ContactSearchRequest": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{
"required": [
"companyLinkedInUrls"
]
},
{
"required": [
"firstName"
]
},
{
"required": [
"lastName"
]
},
{
"required": [
"jobTitles"
]
},
{
"required": [
"keywords"
]
},
{
"required": [
"countries"
]
},
{
"required": [
"departments"
]
},
{
"required": [
"seniorityLevels"
]
},
{
"required": [
"recentlyChangedJobs"
],
"properties": {
"recentlyChangedJobs": {
"const": true
}
}
},
{
"required": [
"yearsInCurrentPosition"
]
},
{
"required": [
"yearsAtCurrentCompany"
]
},
{
"required": [
"industries"
]
},
{
"required": [
"companyHeadcounts"
]
},
{
"required": [
"companyTypes"
]
}
],
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search (note: plural — pass an array even for a single company). Supports multiple companies; results are merged and deduplicated. Omit to search across all companies.\n",
"example": [
"https://linkedin.com/company/google"
]
},
"firstName": {
"type": "string",
"maxLength": 100,
"description": "First name of the contact to search for",
"example": "John"
},
"lastName": {
"type": "string",
"maxLength": 100,
"description": "Last name of the contact to search for",
"example": "Doe"
},
"jobTitles": {
"type": "array",
"items": {
"type": "string",
"maxLength": 200
},
"description": "Job titles to search for (plural — pass an array)",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"maxLength": 500,
"description": "Keywords to search for in contact profiles (e.g., skills, technologies)",
"example": "Kubernetes, DevOps"
},
"countries": {
"type": "array",
"items": {
"type": "string",
"minLength": 2,
"maxLength": 2,
"pattern": "^[A-Za-z]{2}$"
},
"description": "Countries to filter contacts by location. Must be ISO 3166-1 alpha-2 codes (e.g., \"US\", \"GB\", \"DE\") — full country names are not accepted.",
"example": [
"US",
"GB",
"DE"
]
},
"departments": {
"type": "array",
"items": {
"type": "string",
"maxLength": 40,
"enum": [
"Accounting",
"Administrative",
"Arts and Design",
"Business Development",
"Community and Social Services",
"Consulting",
"Customer Success and Support",
"Education",
"Engineering",
"Entrepreneurship",
"Finance",
"Healthcare Services",
"Human Resources",
"Information Technology",
"Legal",
"Marketing",
"Media and Communication",
"Military and Protective Services",
"Operations",
"Product Management",
"Program and Project Management",
"Purchasing",
"Quality Assurance",
"Real Estate",
"Research",
"Sales"
]
},
"description": "Department (function) filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Sales",
"Marketing"
]
},
"seniorityLevels": {
"type": "array",
"items": {
"type": "string",
"maxLength": 25,
"enum": [
"Owner / Partner",
"CXO",
"Vice President",
"Director",
"Experienced Manager",
"Entry Level Manager",
"Strategic",
"Senior",
"Entry Level",
"In Training"
]
},
"description": "Seniority level filter using LinkedIn Sales Navigator taxonomy. Multiple values are OR'd together. Combined with other filters via AND. All values are treated as INCLUDED — exclusion is not currently supported. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"CXO",
"Vice President",
"Director"
]
},
"recentlyChangedJobs": {
"type": "boolean",
"description": "When true, uses Sales Navigator's Changed jobs filter. LinkedIn controls this rolling window and has no caller-selected start date. Approximately 90 days is an unverified working assumption.\n",
"example": true
},
"yearsInCurrentPosition": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years in current position ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"Less than 1 year"
]
},
"yearsAtCurrentCompany": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "string",
"enum": [
"Less than 1 year",
"1 to 2 years",
"3 to 5 years",
"6 to 10 years",
"More than 10 years"
]
},
"description": "Sales Navigator's Years at current company ranges. Multiple values are OR'd together. Combined with other filters via AND. This filter matches the person. For a person with several current positions, it does not identify which position matched.\n",
"example": [
"3 to 5 years"
]
},
"industries": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"maxLength": 200
},
"description": "Current LinkedIn Sales Navigator industry display names. Letter case and surrounding whitespace are normalized. Multiple values are OR'd together. Combined with other filters via AND. See https://docs.saber.app/contacts/industry-values for the canonical values.\n",
"example": [
"Software Development"
]
},
"companyHeadcounts": {
"type": "array",
"minItems": 1,
"maxItems": 9,
"items": {
"type": "string",
"enum": [
"Self-employed",
"1-10",
"11-50",
"51-200",
"201-500",
"501-1,000",
"1,001-5,000",
"5,001-10,000",
"10,001+"
]
},
"description": "Current company headcount ranges. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"51-200",
"201-500"
]
},
"companyTypes": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "string",
"enum": [
"Public Company",
"Privately Held",
"Non Profit",
"Educational Institution",
"Partnership",
"Self Employed",
"Self Owned",
"Government Agency"
]
},
"description": "Current company types. Multiple values are OR'd together. Combined with other filters via AND.\n",
"example": [
"Privately Held"
]
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of contacts to return per page",
"example": 25
},
"offset": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Zero-based offset for pagination. Mutually exclusive with `cursor` — provide one or the other. Pages starting at or past the 1000-result search ceiling are rejected with 422 and error code `PAGE_BEYOND_CEILING` (distinct from the connector-required 422); a page that merely runs past it is clamped to the remaining rows. This compatibility field does not keep successive requests on one Sales Navigator connector. Use `cursor` for safe pagination.\n",
"example": 0
},
"cursor": {
"type": "string",
"maxLength": 100,
"description": "Paging handle from the previous response's `nextCursor`. The documented way to fetch the next page; mutually exclusive with `offset`. Never construct one yourself. Pass it back with the same filters and `limit`. The cursor keeps the pagination session on the connector that served its first page. If that connector becomes unavailable, restart from page 1. Invalid, expired, and older cursor formats return `422 INVALID_CURSOR`.\n"
}
}
},
"Tenure": {
"type": "object",
"properties": {
"years": {
"type": "integer",
"minimum": 0,
"description": "Number of years",
"example": 2
},
"months": {
"type": "integer",
"minimum": 0,
"maximum": 11,
"description": "Number of months",
"example": 6
}
}
},
"ContactSearchResult": {
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "First name of the contact",
"example": "John"
},
"lastName": {
"type": "string",
"description": "Last name of the contact",
"example": "Doe"
},
"fullName": {
"type": "string",
"description": "Full name of the contact",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "Headline/tagline from their profile",
"example": "Building something sellers love"
},
"summary": {
"type": "string",
"description": "Summary/about section from their profile",
"example": "After over a decade of leading product teams..."
},
"positions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactPosition"
},
"description": "Current positions returned for the contact by this search"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date. A year without a month is treated as January. A dated position wins over an undated one. Ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Saber"
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels",
"example": [
"Director",
"VP"
]
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "The contact's LinkedIn Sales Navigator lead URL, in the format `https://www.linkedin.com/sales/lead/`. The lead ID can include a search suffix such as `,NAME_SEARCH,`.",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1gBQZCU123,NAME_SEARCH,abcd"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "Profile avatar URL",
"example": "https://media.licdn.com/..."
},
"location": {
"type": "string",
"description": "Geographic location",
"example": "San Francisco, California"
}
}
},
"ContactSearchResponse": {
"type": "object",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore",
"salesNavConnected"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactSearchResult"
},
"description": "Contacts matching the search on this page"
},
"total": {
"type": "integer",
"description": "Total number of contacts matching the search in LinkedIn. This is the full match count and CAN EXCEED the reachable window: only the first 1000 results are pageable (the search ceiling), so a search with more matches still reports the true total here while `hasMore` and `nextCursor` stop at the ceiling. Do not derive page count from `total` (`ceil(total / limit)` will point past the ceiling and be rejected with `422 PAGE_BEYOND_CEILING`) — page by following `nextCursor` until it is absent, or stop when `hasMore` is false.\n",
"example": 150
},
"limit": {
"type": "integer",
"description": "Page size used for this request",
"example": 25
},
"offset": {
"type": "integer",
"description": "Zero-based offset used for this request",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether another page is reachable under the 1000-result search ceiling. Moves in lockstep with `nextCursor` — `hasMore: false` exactly when `nextCursor` is absent, i.e. once results run out or the next page would start at the ceiling. (This is reachability here, not raw LinkedIn availability: a search with more than 1000 total matches reports `hasMore: false` on the last page below the ceiling. Narrow the filters to reach deeper rows.)\n",
"example": true
},
"salesNavConnected": {
"type": "boolean",
"description": "Whether LinkedIn Sales Navigator is connected for the user",
"example": true
},
"nextCursor": {
"type": "string",
"description": "Handle for the next page; pass it back as `cursor` together with the same filters and `limit`. Absent on the last reachable page — whether because results ran out or because the next page would start at the 1000-result search ceiling.\n"
}
}
},
"CreateSignalTemplateRequest": {
"type": "object",
"required": [
"name",
"question"
],
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contacts_generation",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text",
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\nSee CreateSignalRequest.outputSchema for full documentation.\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType.\nStructure varies by answerType:\n- For percentage/number/currency: { ranges: [{ rangeStart, rangeEnd, answerQualification }] }\n- For boolean: { yes: \"qualification\", no: \"qualification\" }\n- For list: { choices: { key: { answerQualification, label } } }\n\nValid answerQualification values: disqualified, poor, neutral, good, excellent\n",
"additionalProperties": true,
"example": {
"ranges": [
{
"rangeStart": 0,
"rangeEnd": 20,
"answerQualification": "poor"
},
{
"rangeStart": 20,
"rangeEnd": 50,
"answerQualification": "neutral"
},
{
"rangeStart": 50,
"rangeEnd": 100,
"answerQualification": "excellent"
}
]
}
}
}
},
"UpdateSignalTemplateRequest": {
"type": "object",
"description": "Request body for updating a signal template using PATCH semantics.\nAll fields are optional - only provide the fields you want to update.\nOmitted fields will retain their values from the previous template version.\n",
"properties": {
"name": {
"type": "string",
"description": "User-friendly name for the template",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"maxLength": 1000,
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema defining the expected output structure. Required when answerType is \"json_schema\".\n",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
}
}
},
"SignalTemplateResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Stable template identifier — alias for `templateId`. Use this (or `templateId`) when authoring scoring rules. Deprecated; use `templateId` instead.",
"deprecated": true,
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"templateId": {
"type": "string",
"format": "uuid",
"description": "Stable parent template ID. Pass this as `signalTemplateId` when authoring scoring rules.",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"version": {
"type": "integer",
"description": "Version number of this template (starts at 1, increments with each update)",
"example": 1
},
"organizationId": {
"type": "string",
"description": "Organization ID that owns this template",
"example": "org_123"
},
"name": {
"type": "string",
"description": "User-friendly name for the template",
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Optional description of what this template does",
"example": "Identifies which CRM system a company is using"
},
"question": {
"type": "string",
"description": "The research question to ask about companies",
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "The expected format of the answer",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"example": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (present when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "The importance/weight of the signal",
"enum": [
"important",
"nice_to_have",
"not_important"
],
"example": "important"
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria mapping answer values based on answerType",
"additionalProperties": true
},
"createdByUserId": {
"type": "string",
"description": "User ID who created this template version",
"example": "user_123"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When this template version was created",
"example": "2024-10-14T15:30:00Z"
},
"deletedAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When this template version was deleted (null if active)",
"example": null
},
"source": {
"type": "string",
"description": "Source of the template creation",
"enum": [
"api",
"research",
"list-building",
"hubspot"
],
"example": "api"
}
},
"required": [
"id",
"version",
"organizationId",
"name",
"question",
"answerType",
"createdByUserId",
"createdAt",
"source"
]
},
"ListSignalTemplatesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalTemplateResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of templates available",
"example": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of templates returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of templates skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more templates available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreateSignalSubscriptionRequest": {
"type": "object",
"description": "Exactly one of `frequency` or `cronExpression` must be provided.\nUse `frequency` for preset schedules (daily/weekly/monthly) or\n`cronExpression` for custom cron-based schedules.\n",
"properties": {
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "ID of an existing signal template. If omitted, `name` and `question`\nare required to create a template inline.\n",
"example": "e45c1dc4-d422-4b51-956b-cb6d3ddaf250"
},
"name": {
"type": "string",
"description": "Template name (required when creating inline, ignored when signalTemplateId is provided)",
"minLength": 1,
"maxLength": 200,
"example": "CRM Detection"
},
"description": {
"type": "string",
"description": "Template description (optional, for inline creation)",
"maxLength": 1000
},
"question": {
"type": "string",
"description": "Research question (required when creating inline)",
"minLength": 1,
"maxLength": 2000,
"example": "Which CRM are they using?"
},
"answerType": {
"type": "string",
"description": "Expected answer format (for inline creation, defaults to open_text)",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
],
"default": "open_text"
},
"outputSchema": {
"type": "object",
"description": "JSON Schema for custom output (when answerType is json_schema)",
"additionalProperties": true
},
"weight": {
"type": "string",
"description": "Importance of the signal (for inline creation)",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"description": "Qualification criteria for scoring answers (for inline creation)",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression` —\nexactly one must be provided.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency` —\nexactly one must be provided.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling (defaults to UTC)",
"example": "America/New_York"
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID to scope signal execution"
}
},
"required": [
"listId"
]
},
"UpdateSignalSubscriptionRequest": {
"type": "object",
"description": "Replace a subscription's full configuration.\nExactly one of `frequency` or `cronExpression` must be provided.\n",
"required": [
"timezone",
"name",
"question",
"answerType"
],
"properties": {
"frequency": {
"type": "string",
"description": "Preset schedule frequency. Mutually exclusive with `cronExpression`.\n",
"enum": [
"daily",
"weekly",
"monthly"
],
"example": "weekly"
},
"cronExpression": {
"type": "string",
"description": "Custom cron expression (5-field). Mutually exclusive with `frequency`.\n",
"example": "0 9 * * 1,3,5"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling",
"example": "America/New_York"
},
"name": {
"type": "string",
"description": "The subscription display name",
"minLength": 1,
"maxLength": 200
},
"question": {
"type": "string",
"description": "The research question",
"minLength": 1,
"maxLength": 2000
},
"answerType": {
"type": "string",
"description": "Expected answer format",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"description": {
"type": "string",
"description": "Signal description",
"maxLength": 1000
},
"outputSchema": {
"type": [
"object",
"null"
],
"additionalProperties": true,
"description": "JSON schema for json_schema answer type. Send null to clear."
},
"weight": {
"type": "string",
"description": "Signal importance weight",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true,
"description": "Qualification criteria"
}
}
},
"SignalSubscriptionResponse": {
"type": "object",
"description": "A signal subscription merged with its template definition.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The subscription ID (unique per subscription)."
},
"signalTemplateId": {
"type": "string",
"format": "uuid",
"description": "The signal template ID this subscription is based on. Use this to correlate subscriptions that share the same template."
},
"version": {
"type": "integer",
"description": "Template version number"
},
"name": {
"type": "string",
"description": "Subscription name if set, otherwise the template name"
},
"description": {
"type": "string"
},
"question": {
"type": "string",
"description": "The research question"
},
"answerType": {
"type": "string",
"enum": [
"open_text",
"number",
"boolean",
"list",
"percentage",
"currency",
"url",
"contacts",
"contact_posts",
"contact_engagements",
"json_schema"
]
},
"outputSchema": {
"type": "object",
"additionalProperties": true
},
"weight": {
"type": "string",
"enum": [
"important",
"nice_to_have",
"not_important"
]
},
"qualificationCriteria": {
"type": "object",
"additionalProperties": true
},
"frequency": {
"type": "string",
"description": "Preset frequency if set",
"enum": [
"daily",
"weekly",
"monthly"
]
},
"cronExpression": {
"type": "string",
"description": "The resolved cron expression"
},
"timezone": {
"type": "string",
"description": "IANA timezone for scheduling"
},
"status": {
"type": "string",
"enum": [
"active",
"stopped"
]
},
"listId": {
"type": "string",
"format": "uuid",
"description": "Contact list ID"
},
"lastRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"source": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"signalTemplateId",
"version",
"name",
"question",
"answerType",
"cronExpression",
"timezone",
"status",
"listId",
"source",
"createdAt"
]
},
"ListSignalSubscriptionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SignalSubscriptionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of subscriptions matching the query",
"example": 42
},
"limit": {
"type": "integer",
"description": "Maximum number of subscriptions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of subscriptions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more subscriptions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"GenerateSummaryRequest": {
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"description": "The company domain to generate a summary for (e.g., \"acme.com\")",
"pattern": "^[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])?)*$",
"minLength": 1,
"maxLength": 253,
"example": "acme.com"
}
},
"additionalProperties": false
},
"SummaryResponse": {
"type": "object",
"properties": {
"summary": {
"type": [
"array",
"null"
],
"description": "The generated summary as an array of data points, or null if no completed signals exist",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
}
},
"required": [
"summary"
]
},
"SummariesResponse": {
"type": "object",
"properties": {
"results": {
"type": "array",
"description": "Array of summary records for the domain, ordered by creation date (latest first)",
"items": {
"$ref": "#/components/schemas/SummaryRecord"
}
},
"total": {
"type": "integer",
"description": "Total number of summaries for this domain"
},
"limit": {
"type": "integer",
"description": "Maximum results per page"
},
"offset": {
"type": "integer",
"description": "Number of results skipped"
},
"count": {
"type": "integer",
"description": "Number of results in this response"
}
},
"required": [
"results",
"total",
"limit",
"offset",
"count"
]
},
"SummaryRecord": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the summary",
"example": "58f74458-7e71-4ab6-8297-55dab9c752c0"
},
"summary": {
"type": [
"array",
"null"
],
"description": "The summary data points as an array, or null if summary generation failed",
"items": {
"$ref": "#/components/schemas/DataPoint"
}
},
"status": {
"type": "string",
"description": "Status of the summary generation",
"enum": [
"in_progress",
"completed",
"failed"
],
"example": "completed"
},
"signalsCount": {
"type": "integer",
"description": "Number of signals used to generate this summary",
"example": 10
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was created",
"example": "2024-01-15T10:30:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the summary was last updated",
"example": "2024-01-15T10:32:00Z"
}
},
"required": [
"id",
"summary",
"status",
"signalsCount",
"createdAt",
"updatedAt"
]
},
"DataPoint": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Human-readable description of the data point",
"example": "Company operates in the Enterprise Software industry"
},
"referenceQuestions": {
"type": "array",
"description": "Array of signal questions that contributed to this data point",
"items": {
"type": "string"
},
"example": [
"What industry does this company operate in?",
"What is the company's primary business focus?"
]
},
"qualification": {
"type": "string",
"description": "Qualification value indicating the quality/fit of this data point",
"enum": [
"disqualified",
"poor",
"neutral",
"good",
"excellent"
],
"example": "good"
},
"sources": {
"type": "array",
"description": "Array of sources used to generate this data point",
"items": {
"$ref": "#/components/schemas/Source"
}
}
},
"required": [
"description",
"referenceQuestions",
"qualification",
"sources"
]
},
"Source": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"description": "URL of the source",
"example": "https://acme.com/about"
},
"title": {
"type": "string",
"description": "Title of the source",
"example": "About Acme Corp"
},
"snippet": {
"type": "string",
"description": "Optional snippet from the source",
"example": "Leading provider of enterprise software..."
}
},
"required": [
"url",
"title"
]
},
"ChangeType": {
"type": "string",
"description": "Type of change detected between signal versions. INITIAL = first signal version, NO_CHANGE = identical or semantically equivalent to previous, MINOR_CHANGE = low-impact semantic change, CHANGED = significant change (legacy, pre-graduated), MAJOR_CHANGE = high-impact semantic change.\n",
"enum": [
"INITIAL",
"NO_CHANGE",
"CHANGED",
"MINOR_CHANGE",
"MAJOR_CHANGE"
],
"example": "CHANGED"
},
"ChangeDelta": {
"type": "object",
"description": "Structured delta of the change between signal versions.\nThe structure varies based on the answer type:\n- LIST: {type: \"LIST\", added: string[], removed: string[]}\n- NUMBER: {type: \"NUMBER\", previous: number, current: number, change: number}\n- CURRENCY: {type: \"CURRENCY\", previous: number | object, current: number | object, currency?: string, change: number | string}\n- PERCENTAGE: {type: \"PERCENTAGE\", previous: number, current: number, unit: string, change: number}\n- BOOLEAN: {type: \"BOOLEAN\", previous: boolean, current: boolean}\n- GENERIC: {type: \"GENERIC\", previous: Answer | null, current: Answer | null}\n- SEMANTIC: {type: \"SEMANTIC\", previous: Answer | null, current: Answer | null, similarityScore: number}\n- SEMANTIC_LIST: {type: \"SEMANTIC_LIST\", added: string[], removed: string[], matched: [{previous: string, current: string, similarityScore: number}], similarityScore: number}\n- CONTACTS: {type: \"CONTACTS\", added: Contact[], removed: Contact[], updated: [{previous: Contact, current: Contact, fieldChanges: [...]}]}\n- CONTACT_POSTS: {type: \"CONTACT_POSTS\", added: Post[], removed: Post[], updated: [{previous: Post, current: Post, fieldChanges: [...], engagementGrowth?: EngagementGrowth}]}\n",
"additionalProperties": true,
"example": {
"type": "LIST",
"added": [
"Enterprise"
],
"removed": [
"Starter"
]
}
},
"LinkedInCapabilityStatus": {
"type": "object",
"description": "Status of a single LinkedIn capability during signal generation",
"required": [
"capability",
"used"
],
"properties": {
"capability": {
"type": "string",
"enum": [
"contact_search",
"profile_enrichment",
"company_data",
"company_search",
"activity_tracking",
"post_search"
],
"description": "LinkedIn capability name",
"example": "profile_enrichment"
},
"used": {
"type": "boolean",
"description": "Whether this capability was used during signal generation",
"example": true
},
"rateLimited": {
"type": "boolean",
"description": "Whether this capability was rate-limited",
"example": false
}
}
},
"LinkedInContext": {
"type": "object",
"description": "LinkedIn capability usage context for a signal (only present when LinkedIn tools were involved)",
"required": [
"linkedInToolsUsed",
"mode",
"capabilities"
],
"properties": {
"linkedInToolsUsed": {
"type": "boolean",
"description": "Whether any LinkedIn tools were used",
"example": true
},
"mode": {
"type": "string",
"enum": [
"required",
"preferred",
"off"
],
"description": "LinkedIn connector mode used",
"example": "preferred"
},
"capabilities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedInCapabilityStatus"
},
"description": "Per-capability status"
},
"degraded": {
"type": "boolean",
"description": "Whether the signal was degraded due to LinkedIn limitations",
"example": false
}
}
},
"ChangeContext": {
"type": "object",
"description": "Information about changes between signal versions. The changeType field uses graduated change types: INITIAL for first versions, NO_CHANGE for identical or semantically equivalent results, and MINOR_CHANGE/MAJOR_CHANGE for graduated severity (replacing the legacy binary CHANGED type). Subscribers can filter broadly on changedFields (any event on a field) or precisely on addedFields/updatedFields/removedFields without inspecting the delta structure.\n",
"required": [
"changeType",
"changedFields",
"addedFields",
"updatedFields",
"removedFields"
],
"properties": {
"changeType": {
"$ref": "#/components/schemas/ChangeType"
},
"changeDelta": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeDelta"
},
{
"type": "null"
}
],
"description": "Structured delta of the change (structure varies by answer type)"
},
"previousSignalId": {
"type": [
"string",
"null"
],
"description": "ID of the previous signal version",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"example": "feb2e1c1-dc67-49f6-a459-62383c67daf5"
},
"changedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Union of addedFields + updatedFields + removedFields. Use this to subscribe to any event on a field regardless of change type (e.g. changedFields.includes(\"headline\")). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"certifications",
"contacts",
"headline"
]
},
"addedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where new values appeared (new contacts/posts, new certifications, etc.). Entity-level additions use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"contacts",
"certifications"
]
},
"updatedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where existing values were modified (headline changed, engagement updated, etc.). Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"headline",
"engagement.reactionCount"
]
},
"removedFields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fields where values were removed (contacts left, skills removed, etc.). Entity-level removals use \"contacts\" or \"posts\" as the field name. Empty for scalar answer types (number, boolean, etc.) and NO_CHANGE.\n",
"example": [
"positions"
]
}
}
},
"WebhookActionConfig": {
"type": "object",
"description": "Configuration for a webhook action.",
"required": [
"destination"
],
"properties": {
"destination": {
"type": "string",
"format": "uri",
"description": "The URL to deliver webhook payloads to",
"example": "https://example.com/webhook"
}
}
},
"CreateSubscriptionActionRequest": {
"type": "object",
"description": "Create a new action on a subscription. The `type` field determines which\nconfiguration object is required.\n",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
],
"example": "webhook"
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"UpdateSubscriptionActionRequest": {
"type": "object",
"description": "Update the configuration of an existing action. The action type is\nimmutable; only the type-specific configuration can be changed.\n",
"properties": {
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
}
}
},
"SubscriptionActionResponse": {
"type": "object",
"description": "A subscription action resource.",
"required": [
"id",
"subscriptionId",
"type",
"enabled",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The action ID"
},
"subscriptionId": {
"type": "string",
"format": "uuid",
"description": "The subscription this action belongs to"
},
"type": {
"type": "string",
"description": "The action type discriminator",
"enum": [
"webhook"
]
},
"webhook": {
"$ref": "#/components/schemas/WebhookActionConfig"
},
"enabled": {
"type": "boolean",
"description": "Whether the action is enabled. Disabled actions are skipped during dispatch.",
"default": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "When the action was created"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"description": "When the action was last updated"
}
}
},
"ListSubscriptionActionsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubscriptionActionResponse"
}
},
"total": {
"type": "integer",
"description": "Total number of actions for the subscription",
"example": 3
},
"limit": {
"type": "integer",
"description": "Maximum number of actions returned",
"example": 20
},
"offset": {
"type": "integer",
"description": "Number of actions skipped",
"example": 0
},
"hasMore": {
"type": "boolean",
"description": "Whether there are more actions available",
"example": false
}
},
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
]
},
"CreditsBalance": {
"type": "object",
"description": "Remaining credits balance for the organization in the current billing period",
"required": [
"remainingCredits"
],
"properties": {
"remainingCredits": {
"type": "number",
"minimum": 0,
"description": "Number of credits remaining in the current billing period. Each signal creation consumes two credits, and some endpoints charge fractional credit amounts (e.g. 0.1 credits per find-email call), so this value may be fractional. Returns 0 when the organization has no credits left; credits reset at the start of each new billing period.\n",
"example": 57.9
}
}
},
"OrganisationDescription": {
"type": "object",
"description": "Descriptive text fields for the organisation",
"properties": {
"general": {
"type": "string",
"description": "General overview of the organisation",
"example": "Acme Corp builds enterprise software for logistics teams."
},
"products": {
"type": "string",
"description": "Description of the organisation's products or services",
"example": "Route optimisation platform and real-time freight tracking."
},
"useCases": {
"type": "string",
"description": "Key use cases the organisation addresses",
"example": "Reducing delivery costs for 3PL operators and e-commerce brands."
},
"valueProp": {
"type": "string",
"description": "The organisation's core value proposition",
"example": "Cut logistics costs by 20% with AI-powered route optimisation."
}
}
},
"Organisation": {
"type": "object",
"description": "Organisation profile",
"required": [
"name",
"website",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Display name of the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "Organisation website domain",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"UpdateOrganisationRequest": {
"type": "object",
"description": "Fields to update on the organisation profile. All fields are optional.",
"properties": {
"name": {
"type": "string",
"description": "New display name for the organisation",
"example": "Acme Corp"
},
"website": {
"type": "string",
"description": "New website domain for the organisation",
"example": "acme.com"
},
"description": {
"$ref": "#/components/schemas/OrganisationDescription"
}
}
},
"ContactListFilters": {
"type": "object",
"description": "Search filters stored on a contact list",
"properties": {
"companyLinkedInUrls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "LinkedIn company URLs to scope the search. Supports multiple companies. Omit to search across all companies.",
"example": [
"https://www.linkedin.com/company/stripe"
]
},
"jobTitles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Job titles to filter contacts by",
"example": [
"Engineering Manager",
"VP Engineering"
]
},
"keywords": {
"type": "string",
"description": "Keywords to search for in contact profiles",
"example": "Kubernetes"
},
"countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Countries to filter by (ISO 3166-1 alpha-2 codes)",
"example": [
"US",
"GB"
]
}
}
},
"ContactList": {
"type": "object",
"description": "A contact list with metadata and stored search filters",
"required": [
"id",
"name",
"filters",
"contactCount",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the contact list",
"example": "abc123"
},
"name": {
"type": "string",
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
},
"contactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of contacts stored in this list",
"example": 47
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListsResponse": {
"type": "object",
"description": "Paginated list of contact lists",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactList"
}
},
"total": {
"type": "integer",
"description": "Total number of contact lists for this organization",
"example": 3
},
"limit": {
"type": "integer",
"example": 20
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": false
}
}
},
"ContactListItem": {
"type": "object",
"description": "Point-in-time snapshot of a contact's data as returned by Sales Navigator at list creation",
"required": [
"id",
"listId",
"baseContactId",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this list item",
"example": "item_001"
},
"listId": {
"type": "string",
"description": "ID of the parent contact list",
"example": "abc123"
},
"baseContactId": {
"type": "string",
"description": "ID of the underlying base_contacts identity record",
"example": "bc_001"
},
"firstName": {
"type": "string",
"example": "John"
},
"lastName": {
"type": "string",
"example": "Doe"
},
"fullName": {
"type": "string",
"example": "John Doe"
},
"headline": {
"type": "string",
"description": "LinkedIn headline",
"example": "Engineering Manager at Stripe | ex-Google"
},
"role": {
"type": "string",
"description": "Job title from the current position with the latest startedOn date when the list was created. A year without a month is treated as January, dated positions win over undated ones, and ties keep the existing response order.",
"example": "Engineering Manager"
},
"companyName": {
"type": "string",
"description": "Company name from the same position as role.",
"example": "Stripe"
},
"location": {
"type": "string",
"example": "San Francisco, California"
},
"avatar": {
"type": "string",
"format": "uri",
"description": "URL to the contact's profile photo",
"example": "https://media.licdn.com/dms/image/..."
},
"seniority": {
"type": "array",
"items": {
"type": "string"
},
"description": "Seniority levels assigned by Sales Navigator (e.g. MANAGER, DIRECTOR, VP)",
"example": [
"MANAGER"
]
},
"positions": {
"type": "array",
"description": "All current positions returned by Sales Navigator when the list was created",
"items": {
"$ref": "#/components/schemas/ContactPosition"
}
},
"linkedInSalesNavigatorProfileUrl": {
"type": "string",
"format": "uri",
"description": "Direct link to the contact's Sales Navigator profile",
"example": "https://www.linkedin.com/sales/lead/ACwAAABOW1g"
},
"createdAt": {
"type": "string",
"format": "date-time",
"example": "2026-03-03T12:00:00Z"
}
}
},
"ContactListContactsResponse": {
"type": "object",
"description": "Paginated list of contacts within a contact list",
"required": [
"items",
"total",
"limit",
"offset",
"hasMore"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ContactListItem"
}
},
"total": {
"type": "integer",
"description": "Total number of contacts in this list",
"example": 47
},
"limit": {
"type": "integer",
"example": 25
},
"offset": {
"type": "integer",
"example": 0
},
"hasMore": {
"type": "boolean",
"example": true
}
}
},
"CreateContactListRequest": {
"type": "object",
"description": "Request body for creating a contact list",
"required": [
"name",
"filters"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "Display name for the contact list",
"example": "Engineering leads at Stripe"
},
"filters": {
"$ref": "#/components/schemas/ContactListFilters"
}
}
},
"UpdateContactListRequest": {
"type": "object",
"description": "Request body for renaming a contact list",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"description": "New display name for the contact list",
"example": "Senior engineers at Stripe"
}
}
},
"ScoringProfile": {
"type": "object",
"description": "Named, org-scoped scoring configuration that groups rules for a specific object type.",
"required": [
"id",
"organizationId",
"type",
"name",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"example": "442932e4-9a9f-48c0-a79e-6366a732a52b"
},
"organizationId": {
"type": "string",
"example": "66575d79e8350cba826a36b1"
},
"type": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile scores. Immutable after creation."
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount in EMEA"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateScoringProfileRequest": {
"type": "object",
"required": [
"profileType",
"name"
],
"properties": {
"profileType": {
"type": "string",
"enum": [
"company",
"contact"
],
"description": "Object type this profile will score"
},
"name": {
"type": "string",
"example": "EMEA Enterprise"
},
"description": {
"type": [
"string",
"null"
]
}
}
},
"UpdateScoringProfileRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Enterprise ICP"
},
"description": {
"type": [
"string",
"null"
],
"example": "Companies with 1000+ headcount"
}
}
},
"ScoringPointValues": {
"type": "object",
"description": "Point-value mapping for one rule. The shape varies by the answer type of the\nreferenced signal template — exactly one of `true`/`false`, `ranges`, or `choices`\nis populated.\n",
"example": {
"ranges": [
{
"min": 0,
"max": 100,
"points": 2
},
{
"min": 100,
"max": 1000,
"points": 10
},
{
"min": 1000,
"max": 100000,
"points": 25
}
]
},
"properties": {
"true": {
"type": "number",
"description": "Points awarded when a boolean signal answers `true`. Used by `boolean` answer types.",
"example": 20
},
"false": {
"type": "number",
"description": "Points awarded when a boolean signal answers `false`. Used by `boolean` answer types.",
"example": -5
},
"ranges": {
"type": "array",
"description": "Used by `number`, `percentage`, and `currency` answer types. Upper bound is exclusive.",
"items": {
"type": "object",
"required": [
"min",
"max",
"points"
],
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"points": {
"type": "number"
}
}
}
},
"choices": {
"type": "object",
"description": "Used by `list` answer types. Map of allowed list value → points awarded if present in the answer.",
"additionalProperties": {
"type": "number"
},
"example": {
"Salesforce": 10,
"HubSpot": 8,
"None": -10
}
},
"mode": {
"type": "string",
"enum": [
"additive",
"best-match",
"contains-all",
"contains-none",
"exact-match"
],
"description": "Optional. Controls how a `list` rule combines matched choices. Defaults\nto `additive` (sum matched, clamp to highest single choice) — the v1\nbehaviour. Other modes:\n\n* `best-match` — award only the highest-scoring matched choice.\n* `contains-all` — award `max(choices)` only if every configured choice appears.\n* `contains-none` — award `max(choices)` only if none of the configured choices appear.\n* `exact-match` — award `sum(choices)` only when the answer set equals the configured set exactly.\n",
"example": "additive"
}
}
},
"ScoringRule": {
"type": "object",
"description": "Maps one signal template to point values for a given dimension within a profile.",
"required": [
"id",
"profileId",
"signalTemplateId",
"dimension",
"pointValues",
"createdAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string",
"description": "Stable parent template ID (not the version ID). Resolved at compute time across versions."
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"selector": {
"type": [
"string",
"null"
],
"description": "Reserved for future selector-based scoring (jsonpath, list filters).\nAlways `null` in v1; multi-selector rules become possible in a later\nrelease without a follow-up migration.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"UpsertScoringRuleRequest": {
"type": "object",
"required": [
"signalTemplateId",
"dimension",
"answerType",
"pointValues"
],
"properties": {
"signalTemplateId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"answerType": {
"type": "string",
"enum": [
"boolean",
"number",
"percentage",
"currency",
"list"
],
"description": "Drives shape validation for `pointValues`. Must match the referenced\nsignal template's answer type — mismatch surfaces as an\n`INVALID_POINT_VALUES` 422 at write time rather than a silent\ncompute failure later.\n"
},
"pointValues": {
"$ref": "#/components/schemas/ScoringPointValues"
}
}
},
"ProfileAssignment": {
"type": "object",
"description": "Links a scoring profile to one company or contact.",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"assignedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"description": "For companies, the domain (e.g. `acme.com`); for contacts, the LinkedIn profile URL.",
"example": "acme.com"
},
"assignedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateProfileAssignmentRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectId"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "For `company`, the domain; for `contact`, the LinkedIn profile URL."
}
}
},
"BulkCreateProfileAssignmentsRequest": {
"type": "object",
"required": [
"profileId",
"objectType",
"objectIds"
],
"properties": {
"profileId": {
"type": "string",
"format": "uuid"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "Capped at 500 per request. Paginate larger lists by issuing\nmultiple requests.\n",
"example": [
"acme.com",
"stripe.com",
"framer.com"
]
}
}
},
"ScoreContribution": {
"type": "object",
"description": "How a single rule contributed to a dimension score.",
"required": [
"ruleId",
"signalTemplateId",
"matchedValue",
"pointsEarned",
"maxPoints"
],
"properties": {
"ruleId": {
"type": "string",
"format": "uuid"
},
"signalTemplateId": {
"type": "string"
},
"matchedValue": {
"type": "string",
"description": "Human-readable summary of which choice/range/boolean fired for this rule.\nFormat depends on the rule's answer type and (for list rules) `mode`:\n\n* boolean — `\"true\"` / `\"false\"`\n* number/percentage/currency — `\"250 (100–500)\"` (value plus matched range)\n* list `additive` — comma-joined matched choices, e.g. `\"Salesforce, HubSpot\"`, or `\"no match\"`\n* list `best-match` — the single highest-scoring matched choice, e.g. `\"VP\"`, or `\"no match\"`\n* list `contains-all` — `\"A, B, C\"` when satisfied, `\"missing: B, C\"` (alphabetically sorted) when not\n* list `contains-none` — `\"none present\"` when satisfied, `\"found: X, Y\"` when not\n* list `exact-match` — comma-joined matched choices when the answer set equals the configured set, otherwise `\"set mismatch\"`\n",
"example": "874 (100–1000)"
},
"pointsEarned": {
"type": "number",
"description": "Points this rule contributed. Always in `[0, maxPoints]`.",
"example": 10
},
"maxPoints": {
"type": "number",
"description": "Maximum points this rule could have contributed.",
"example": 25
}
}
},
"ScoreResult": {
"type": "object",
"description": "Latest computed score for one `(profile, object, dimension)` triple. Includes the\nper-rule contribution breakdown so the score is always explainable, plus a delta\nview of the previous score.\n",
"required": [
"id",
"profileId",
"organizationId",
"objectType",
"objectId",
"dimension",
"score",
"contributions",
"signalCoverage",
"totalRules",
"computedAt",
"version"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"profileId": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string"
},
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectId": {
"type": "string"
},
"dimension": {
"type": "string",
"enum": [
"fit",
"urgency"
]
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Score in `[0, 100]` computed as `(earned / maxPossible) * 100`. **All rules\ncount toward the denominator** regardless of whether a signal answer exists,\nso low-coverage scores stay conservatively low.\n",
"example": 40
},
"previousScore": {
"type": [
"number",
"null"
],
"description": "Score from the previous compute, or `null` on first compute.",
"example": 35
},
"contributions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"previousContributions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/ScoreContribution"
}
},
"signalCoverage": {
"type": "integer",
"description": "Number of rules with a signal answer available at compute time.",
"example": 1
},
"totalRules": {
"type": "integer",
"description": "Total rules in the profile/dimension.",
"example": 1
},
"computedAt": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "integer",
"description": "Increments on every recompute.",
"example": 3
}
}
},
"ComputeScoresRequest": {
"type": "object",
"required": [
"objectType",
"objectIds"
],
"properties": {
"objectType": {
"type": "string",
"enum": [
"company",
"contact"
]
},
"objectIds": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": "For `company`, domains; for `contact`, LinkedIn profile URLs.\nCapped at 500 per request — paginate larger lists.\n"
}
}
},
"ComputeScoresAcceptedResponse": {
"type": "object",
"description": "Returned by `POST /v1/scoring/compute` with status 202 once recompute\nrecompute jobs have been queued. `failed > 0` means some dispatches\nfailed (logged server-side); the request as a whole is still \"accepted\"\nsince at least one job was queued. If every dispatch fails the endpoint\nreturns 502 instead.\n",
"required": [
"queued",
"failed"
],
"properties": {
"queued": {
"type": "integer",
"minimum": 0,
"example": 5,
"description": "Number of objects whose recompute job was successfully queued."
},
"failed": {
"type": "integer",
"minimum": 0,
"example": 0,
"description": "Number of objects whose recompute dispatch failed."
}
}
}
}
},
"x-tagGroups": [
{
"name": "Enrichments",
"tags": [
"Enrichments",
"Enrichment Templates",
"Summaries"
]
},
{
"name": "Monitors",
"tags": [
"Monitors"
]
},
{
"name": "Contacts",
"tags": [
"Contacts"
]
},
{
"name": "Legacy (deprecated)",
"tags": [
"Signals",
"Signals Templates",
"Signal Subscriptions",
"Subscription Actions",
"Signals Summaries",
"Market Signals"
]
},
{
"name": "Lists",
"tags": [
"Company Lists",
"Contact Lists"
]
},
{
"name": "Platform",
"tags": [
"Companies",
"Billing",
"Connectors",
"Organisation",
"Scoring",
"Email Finder"
]
}
],
"tags": [
{
"name": "Monitors",
"summary": "Monitors",
"kind": "nav",
"x-displayName": "Monitors",
"x-docs-preview": {
"seoDescription": "Create standing monitors that watch lists or the market and emit signals over time.",
"ogSubtitle": "Standing watches that emit signals."
},
"description": "## Monitors\n\nA **monitor** is a standing watch that emits **signals** over time:\nwatch monitors re-check the entities on one of your lists with an\nenrichment template and emit `result_changed` when an answer changes;\ndiscovery monitors watch the market for occurrences (funding raised,\njobs posted, IPO stages). Configuration, lifecycle, and reads are\nfree — each emitted signal is charged under the `monitor-signals`\nfeature (see /pricing for the current rate; silent no-change watch\nruns are free). The legacy `/v1/companies/signals/subscriptions` and\n`/v1/market-signals` routes keep working (and keep their pricing)\nuntil their announced sunset.\n"
},
{
"name": "Enrichment Templates",
"summary": "Enrichment Templates",
"kind": "nav",
"x-displayName": "Enrichment Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable enrichment templates — user-authored custom questions usable across company and contact enrichments.",
"ogSubtitle": "Reusable custom-question templates for enrichments."
},
"description": "## Enrichment templates\n\nA **template** is a user-authored custom question you can reuse across\nenrichments (`enrich/custom` accepts a `signalTemplateId`). Templates\nare entity-agnostic and free to manage. Distinct from enrichment\n*types*, which are system-registered contracts (funding, tech-stack, …).\nThe legacy `/v1/companies/signals/templates` routes keep working until\ntheir announced sunset.\n"
},
{
"name": "Summaries",
"summary": "Summaries",
"kind": "nav",
"x-displayName": "Summaries",
"x-docs-preview": {
"seoDescription": "Generate AI summaries over a company's completed enrichments.",
"ogSubtitle": "One summary over the enrichments you already ran."
},
"description": "## Summaries\n\nA **summary** condenses a company's completed enrichments into one\nnarrative. Generation is metered (one LLM pass over answers you\nalready paid for); reads are free. The legacy\n`/v1/companies/signals/summaries` routes keep working — and stay free —\nuntil their announced sunset.\n"
},
{
"name": "Contacts",
"summary": "Contacts",
"kind": "nav",
"x-displayName": "Contacts",
"x-docs-preview": {
"seoDescription": "Research and search contacts — open-ended AI dossiers and Sales Navigator search.",
"ogSubtitle": "Contact research and search."
},
"description": "## Contacts\n\nOpen-ended contact research (an AI-written dossier from a LinkedIn\nprofile) and live contact search over Sales Navigator.\n"
},
{
"name": "Billing",
"summary": "Billing",
"kind": "nav",
"x-displayName": "Billing",
"x-docs-preview": {
"seoDescription": "Check your organization's credit balance.",
"ogSubtitle": "Credit balance and usage."
},
"description": "## Billing\n\nCredit balance reads. Per-operation credit costs are annotated on each\noperation (`x-credit-cost`) and listed on the Pricing page.\n"
},
{
"name": "Companies",
"summary": "Companies",
"kind": "nav",
"x-displayName": "Companies",
"x-docs-preview": {
"seoDescription": "Search companies by firmographic filters.",
"ogSubtitle": "Company search."
},
"description": "## Companies\n\nCompany search returns blurred previews for free; revealing companies\nis priced through list creation (quoted up-front by count-preview).\n"
},
{
"name": "Enrichments",
"summary": "Enrichments",
"kind": "nav",
"x-displayName": "Enrichments",
"x-docs-preview": {
"seoDescription": "Entity-first enrichment endpoints — ask custom questions about companies and contacts and poll runs through one unified resource.",
"ogSubtitle": "Entity-first enrichment with an explicit run resource."
},
"description": "## Enrichments\n\nThe entity-first successors of signals: `POST\n/v1/{companies,contacts}/enrich/custom` run the same research engine at\nthe same price as custom signals, the prebuilt types (`POST\n/v1/companies/enrich/{firmographics,funding,mna,open-jobs,tech-stack}`)\nrun the prebuilt signal contracts at their own per-type prices, and\nevery execution is an **enrichment run** you can poll at\n`GET /v1/enrichment-runs/{runId}`. The legacy `/signals` paths keep\nworking (and keep their pricing) until their announced sunset — a\nresult either surface produced is served from the shared cache, and\nyour organization never pays twice for the same answer.\n"
},
{
"name": "Signals",
"summary": "Signals",
"kind": "nav",
"x-displayName": "Signals",
"x-docs-preview": {
"seoDescription": "Create and retrieve Saber signals — prebuilt company signals and custom research questions — via sync, async, and batch endpoints.",
"ogSubtitle": "Create and retrieve prebuilt and custom signals through one API surface."
},
"description": "## Signals\n\nA **signal** is an AI-powered research request that answers a specific\nquestion about a company or contact. Saber researches the target,\nverifies findings against public sources, and returns a structured\nanswer with confidence scores and citations.\n\n**Prebuilt signals** answer the most common company questions from just\na domain — no question or answer type to design:\n\n| Signal | Key | Answers |\n|---|---|---|\n| [Funding](/signals/funding) | `funding` | Most recent funding and public/IPO status |\n| [M&A](/signals/mna) | `mna` | Whether the company was acquired or made acquisitions |\n| [Tech](/signals/tech) | `tech` | Which ERP/CRM it uses, or whether it uses a specific technology |\n| [Open Jobs](/signals/open-jobs) | `open-jobs` | What the company is currently hiring for |\n| [Firmographics](/signals/firmographics) | `firmographics` | Base profile plus a refined Saber category |\n\nEach is available **async** (`POST /v1/signals/{key}` — returns a\n`processing` signal to poll or receive via `webhookUrl`) and **sync**\n(`POST /v1/signals/sync/{key}` — blocks until the result is ready;\nrecommended for interactive and agent workflows). Every completed\nanswer is a `json_schema` payload conforming to the signal's documented\nanswer schema. **Cost:** 2 credits per run.\n\n**[Custom signals](/signals/custom)** ask your own question in your own answer shape —\nsupply `question`, `answerType`, and optionally `outputSchema` to\n`POST /v1/companies/signals/sync` (company) or\n`POST /v1/contacts/signals/sync` (contact), with async and batch\nvariants below. Supported answer types: text, numbers, booleans, lists,\npercentages, currency, URLs, and contacts.\n\n**Reading results:** prebuilt and custom company signals share the same\nread path — poll [`GET /v1/companies/signals/{signalId}`](#get-a-company-signal-by-id),\nlist signals with filtering, or supply a `webhookUrl` on create to have\nthe completed signal delivered to your endpoint.\n\nGuides for every signal live in the [Signals section](/signals).\n"
},
{
"name": "Market Signals",
"summary": "Market Signals",
"kind": "nav",
"x-displayName": "Market Signals",
"x-docs-preview": {
"seoDescription": "Configure market signal subscriptions that monitor jobs, LinkedIn posts, funding events, IPO activity, and other buying signals.",
"ogSubtitle": "Monitor market events and deliver matching signals to your workflows."
},
"description": "## Market Signals\n\nMarket Signals let you set up automated monitoring subscriptions that continuously scan external data\nsources and deliver matching signals to your webhook.\n\n**Supported signal types:**\n\n| Type | What it monitors |\n|---|---|\n| `JOB_POSTS` | New job postings matching title, skill, company, and location criteria |\n| `LINKEDIN_POST` | LinkedIn posts matching keyword and AI-based persona/content filters (requires Sales Navigator) |\n| `FUND_RAISED` | Fund closings and fundraising events (web news + RSS + SEC EDGAR Form D) |\n| `RECENT_INVESTMENT` | Recent investment rounds and funding announcements (web news + RSS) |\n| `IPO` | IPO filings and public listing announcements (web news + RSS; for filing-derived coverage use `IPO_LIFECYCLE`) |\n| `IPO_LIFECYCLE` | Deterministic US IPO lifecycle events — filing, amendment, pricing, prospectus, listing, Form 5500 (SEC EDGAR + newswires + DOL EFAST) |\n\n**Typical workflow:**\n\n1. `POST /v1/market-signals/subscriptions` - Create a subscription with type, filters, and webhook URL\n2. Subscription automatically polls on the configured interval (daily or weekly)\n3. New matching signals are delivered to your webhook as batched payloads\n4. `GET /v1/market-signals/subscriptions/{id}/signals` - Browse delivered signals\n5. Use pause/resume/trigger endpoints to control the subscription lifecycle\n\n**AI-powered filter generation (JOB_POSTS):**\n\nInstead of manually defining filters, you can provide a natural language `prompt` and the AI\nwill generate complete filter configurations. You can also combine a prompt with partial filters\nfor hybrid mode, where the AI augments your filters with additional criteria inferred from\nthe prompt.\n\n**LinkedIn integration:**\n\nLINKEDIN_POST subscriptions require a connected LinkedIn Sales Navigator account via the\nSaber Chrome extension. The subscription uses boolean keyword search combined with AI-based\ncontent quality and persona/company filtering to surface high-relevance posts.\n"
},
{
"name": "Contact Lists",
"summary": "Contact Lists",
"kind": "nav",
"x-displayName": "Contact Lists",
"x-docs-preview": {
"seoDescription": "Create, list, update, delete, and page through Saber contact lists built from LinkedIn Sales Navigator search snapshots.",
"ogSubtitle": "Build reusable contact lists from Sales Navigator searches and page through stored snapshots."
},
"description": "## Contact Lists\n\nContact Lists let you capture a snapshot of people at a company who match specific criteria — job titles, keywords, and/or countries — using LinkedIn Sales Navigator.\n\n**Typical workflow:**\n1. `POST /v1/contacts/lists` — provide a company LinkedIn URL and at least one filter; the API runs a live Sales Navigator search and stores the results as a snapshot\n2. `GET /v1/contacts/lists/{listId}/contacts` — page through the stored contacts at any time, no new Sales Navigator call needed\n3. `GET /v1/contacts/lists` — list all your saved contact lists\n4. `PUT /v1/contacts/lists/{listId}` — rename a list\n5. `DELETE /v1/contacts/lists/{listId}` — remove a list and its contacts\n\n**What gets stored:**\nEach contact in the list is stored as a point-in-time snapshot including name, role, company, location, headline, seniority, positions, avatar, and Sales Navigator profile URL. The snapshot reflects what Sales Navigator returned at creation time and is not updated automatically.\n\n**Sales Navigator requirement:**\nCreating a list requires a LinkedIn Sales Navigator connection on your account. If Sales Navigator is not connected, the `POST` returns a connector error and no list is created. Reading existing lists (`GET`) works regardless of connector status.\n"
},
{
"name": "Signals Templates",
"summary": "Signal Templates",
"kind": "nav",
"x-displayName": "Signal Templates",
"x-docs-preview": {
"seoDescription": "Create and manage reusable Saber signal templates that standardize research questions, answer types, and qualification criteria.",
"ogSubtitle": "Standardize recurring research questions with reusable signal templates."
},
"description": "## Signals Templates\n\nCreate reusable signal templates to standardize your research questions across multiple companies.\n\n**Benefits:**\n- Consistent research questions across your organization\n- Reuse qualification criteria and answer types\n- Efficient batch signal creation\n- Version control for your research methodology\n\n**Workflow:**\n1. Create a template with your question and criteria\n2. Use the template ID when creating signals (or in batch requests)\n3. Update templates as needed - changes create new versions\n4. Delete templates when no longer needed (soft-delete for history)\n\nTemplates support all answer types (text, number, boolean, list, etc.) and\nqualification criteria to automatically score companies based on their answers.\n"
},
{
"name": "Signal Subscriptions",
"summary": "Signal Subscriptions",
"kind": "nav",
"x-displayName": "Signal Subscriptions",
"x-docs-preview": {
"seoDescription": "Schedule recurring Saber signal runs against domains, lists, and templates, then manage subscription lifecycle and delivery.",
"ogSubtitle": "Schedule recurring signal runs for ongoing account and contact monitoring."
},
"description": "## Signal Subscriptions\n\nSubscriptions schedule recurring signal executions against a signal template.\nEach subscription ties a template to a frequency, timezone, and optional\ntargeting (list or domain).\n\n**Lifecycle:**\n1. Create a subscription via `POST /v1/companies/signals/subscriptions`\n (references an existing template or creates one inline)\n2. Activate it with `POST /v1/companies/signals/{signalId}/start`\n3. Optionally update schedule parameters via `PUT .../subscriptions/{id}`\n4. Pause with `POST /v1/companies/signals/{signalId}/stop`\n\nThe response merges the template definition with the schedule configuration\nso you get the full picture in a single object.\n"
},
{
"name": "Subscription Actions",
"summary": "Subscription Actions",
"kind": "nav",
"x-displayName": "Subscription Actions",
"x-docs-preview": {
"seoDescription": "Configure how Saber signal subscription results are delivered, including webhook actions for recurring research workflows.",
"ogSubtitle": "Deliver recurring signal results to webhook-based downstream workflows."
},
"description": "## Subscription Actions\n\nActions define delivery mechanisms for subscription results. Each action\nspecifies where and how signal outputs should be sent when the subscription\ntriggers.\n\n**Supported action types:**\n- `webhook` — Deliver results to an HTTP endpoint\n\nMultiple actions of the same type are allowed on a single subscription,\nup to a maximum of **10 actions** per subscription.\n\n> **Note:** Subscription action webhooks are currently delivered without\n> HMAC signatures. Do not rely on request signatures to authenticate\n> incoming payloads from subscription actions.\n"
},
{
"name": "Signals Summaries",
"summary": "Signal Summaries",
"kind": "nav",
"x-displayName": "Signal Summaries",
"x-docs-preview": {
"seoDescription": "Generate structured Saber summaries that consolidate multiple signal answers into cited company intelligence and scoring inputs.",
"ogSubtitle": "Consolidate multiple signal answers into structured, cited account summaries."
},
"description": "## Signals Summaries\n\nGenerate AI-powered summaries that consolidate insights from multiple signals into\nstructured, actionable data points.\n\n**Use cases:**\n- Create company profiles from multiple research signals\n- Generate qualification scorecards\n- Build comprehensive intelligence reports\n- Track changes over time\n\n**How it works:**\n1. Create multiple signals for a domain\n2. Wait for signals to complete (or use batch with `generateSummaryOnComplete: true`)\n3. Request a summary via POST `/v1/companies/signals/summaries`\n4. Retrieve the summary with structured data points and qualifications\n\nEach data point includes:\n- Description of the insight\n- Reference questions used\n- Qualification score (disqualified → excellent)\n- Supporting sources with URLs and snippets\n\nSummaries can be automatically generated when batch signals complete, making it easy\nto research multiple companies in parallel.\n"
},
{
"name": "Company Lists",
"summary": "Company Lists",
"kind": "nav",
"x-displayName": "Company Lists",
"x-docs-preview": {
"seoDescription": "Preview, save, update, and browse company lists for reusable account segments, scoring, signal runs, and CRM workflows.",
"ogSubtitle": "Create reusable company segments for signal research, scoring, and CRM workflows."
},
"description": "## Company Lists\n\nCompany Lists let you build and manage curated sets of companies based on\nfilter criteria such as industry, size, location, and more.\n\n**Typical workflow:**\n1. `POST /v1/companies/search` — preview up to 50 companies matching a filter (no list created)\n2. `POST /v1/companies/lists` — save the filter as a named list\n3. `GET /v1/companies/lists/{listId}/companies` — page through matching companies\n4. `PUT /v1/companies/lists/{listId}` — update the list name or filter\n5. `DELETE /v1/companies/lists/{listId}` — remove the list\n\n**Caching:** Results are cached for 5 minutes per unique filter and caller.\nWrite operations (create, update, delete) automatically invalidate the cache\nfor the authenticated caller.\n"
},
{
"name": "Connectors",
"summary": "Connectors",
"kind": "nav",
"x-displayName": "Connectors",
"x-docs-preview": {
"seoDescription": "Inspect Saber connector status for CRM, HubSpot, LinkedIn Sales Navigator, and other integrations used by API workflows.",
"ogSubtitle": "Verify the integrations your Saber workflows depend on before running them."
},
"description": "## Connectors\n\nConnectors are integrations that link your CRM and sales tools to Saber, enabling enriched\nresearch and automated data syncing.\n\nUse this section to list all configured connectors and inspect their connection status.\nThis is useful for verifying that the integrations your workflows depend on are active before\nrunning signals or batch operations.\n\n**Supported connectors:**\n- **LinkedIn Sales Navigator** (`salesNavigator`) — Contact search and profile enrichment\n- **HubSpot App** (`hubspotApp`) — OAuth-based HubSpot marketplace app installation\n"
},
{
"name": "Organisation",
"summary": "Organization",
"kind": "nav",
"x-displayName": "Organization",
"x-docs-preview": {
"seoDescription": "Read and update organization profile context that Saber uses to personalize CLI, MCP, and API-driven research workflows.",
"ogSubtitle": "Manage organization context for personalized Saber workflows."
},
"description": "## Organisation\n\nRead and update your organisation's profile data: name, website, and description fields\n(general overview, products, use cases, value proposition).\n\nThis endpoint is designed for use with the Saber CLI and MCP server so that tools like\nAI coding assistants can automatically pull in your organisation context and use it when generating\noutreach, research summaries, or signal queries — without requiring manual copy-paste.\n"
},
{
"name": "Scoring",
"summary": "Scoring",
"kind": "nav",
"x-displayName": "Scoring",
"x-docs-preview": {
"seoDescription": "Configure Saber scoring profiles, rules, assignments, and score computations for company and contact prioritization.",
"ogSubtitle": "Turn signal results into explainable fit and urgency scores."
},
"description": "## Native scoring framework\n\nConfigure how Saber turns signal results into structured, explainable **fit** and\n**urgency** scores (0–100) for companies and contacts. Scoring sits on top of the\nsignal layer — the engine is signal-agnostic, so any data the signal layer can ingest\nbecomes scoreable.\n\n### How it fits together\n\n1. **Profile** — a named, org-scoped configuration bound to a single object type\n (`company` or `contact`). E.g. *\"EMEA Enterprise\"*, *\"Mid-Market SaaS\"*. A single\n object can be assigned multiple profiles of the matching type.\n2. **Rule** — within a profile, maps one signal template to point values for one\n dimension (`fit` or `urgency`). The shape of `pointValues` depends on the signal's\n answer type (boolean, number/percentage/currency ranges, or list choices).\n3. **Assignment** — links a profile to a specific company or contact. Triggers an\n immediate recompute so the score appears without waiting for the next signal run.\n4. **Score result** — the computed score for one `(profile, object, dimension)`\n triple. Includes a per-rule contribution breakdown so the number is always\n explainable, plus the previous score for delta views.\n\n### Computation semantics\n\nScore is `(earned / maxPossible) * 100`. **All rules in a profile/dimension count\ntoward the denominator regardless of whether a signal answer exists** — unanswered\nrules earn 0 points but their max still counts. This keeps low-coverage scores\nconservatively low so they don't falsely look high. `signalCoverage` and `totalRules`\nare returned alongside the score so consumers can see how complete it is.\n\n### When scores recompute\n\nIn v1 scoring is manual-trigger:\n\n- Assigning a profile to an object → immediate recompute\n- Upserting or deleting a rule → recompute every object assigned to the profile\n- Calling `POST /v1/scoring/compute` directly\n\nAuto-trigger on signal completion is on the v1.5 roadmap.\n"
},
{
"name": "Email Finder",
"description": "## Email Finder API\n\nResolve a verified email address for a contact when you know their\nfull name and the domain of their employer. Functional parity with\nthe equivalent Findymail endpoint so existing integrations can swap\nproviders with minimal client-side change.\n\n### How it works\n\nSaber maintains a global, hit-counted pattern store. Common\ncorporate email shapes (`{first}.{last}`, `{first}{last}`,\n`{f}{last}`, etc.) are tried in order of historical success at the\nrequested domain — when a known pattern verifies, the lookup\nreturns in ~1 second. Domains we've never seen before fall through\nto a parallel sweep of every common template against the\nverification engine, finding a match in ~2-5 seconds.\n\n### Pricing & rate limits\n\nv1 endpoint is unmetered (no per-call credit charge). Standard\n`/public/v1` rate limits apply.\n\n### What we store\n\nThe pattern templates themselves (e.g. `{first}.{last}`) are\ncached server-side for future warm-path lookups. The returned\nemail and the contact name are **never** persisted by Saber — the\nresponse is the only place they appear, and you're responsible\nfor storing them if your application needs them.\n"
}
]
}